diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..1dbeecde --- /dev/null +++ b/.clang-format @@ -0,0 +1,7 @@ +BasedOnStyle: Chromium +TabWidth: 2 +IndentWidth: 2 +SortIncludes: CaseInsensitive +Language: Cpp +Standard: c++20 +QualifierAlignment: Right diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 00000000..81fa7aa2 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,30 @@ +--- +Checks: "*, + -abseil-*, + -altera-*, + -android-*, + -fuchsia-*, + -google-*, + -llvm*, + -modernize-use-trailing-return-type, + -zircon-*, + -readability-else-after-return, + -readability-static-accessed-through-instance, + -readability-avoid-const-params-in-decls, + -cppcoreguidelines-non-private-member-variables-in-classes, + -misc-non-private-member-variables-in-classes, +" +WarningsAsErrors: '' +HeaderFilterRegex: '' +FormatStyle: none + +CheckOptions: + - key: readability-identifier-length.IgnoredVariableNames + value: 'x|y|z|i|j|k|G|Q' + - key: readability-identifier-length.IgnoredParameterNames + value: 'x|y|z|i|j|k|G|Q' + + + + + diff --git a/.gersemirc b/.gersemirc new file mode 100644 index 00000000..f18049c9 --- /dev/null +++ b/.gersemirc @@ -0,0 +1,10 @@ +cache: true +color: false +definitions: [cmake] +indent: 4 +line_length: 80 +list_expansion: favour-inlining +quiet: false +unsafe: false +warn_about_unknown_commands: true +workers: max \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index edcd689d..b3700421 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: C/C++ CI +name: Linux MPI CI on: push: @@ -11,31 +11,103 @@ on: env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + CMAKE_VERSION: "4.3.4" + VCPKG_BASELINE: "3f7b5a12ef0a55e7b59339b2b69cac4b56d6dbf9" + VCPKG_MAX_CONCURRENCY: "2" + CMAKE_BUILD_PARALLEL_LEVEL: "2" + CTEST_PARALLEL_LEVEL: "2" + OMPI_MCA_rmaps_base_oversubscribe: "1" + OMPI_MCA_hwloc_base_binding_policy: none jobs: - build: - runs-on: ubuntu-latest - continue-on-error: true + mpi: + name: ${{ matrix.compiler }} / ${{ matrix.mpi }} + runs-on: ubuntu-24.04 + timeout-minutes: 90 strategy: + fail-fast: false matrix: - compiler: - - { name: Clang, cc: clang, cxx: clang++ } - - { name: GNU, cc: gcc, cxx: g++ } - argument: ["", "BUILDPYTHONMODULE", "-DDETERMINISTIC_PARHIP=On"] - timeout-minutes: 60 + include: + - compiler: GNU + cc: gcc + cxx: g++ + preset: ci-mpi-gcc + mpi: OpenMPI 4.1 (MPI 3.1 floor) + mpi_packages: libopenmpi-dev openmpi-bin + capability_profile: mpi3-floor + - compiler: Clang + cc: clang + cxx: clang++ + preset: ci-mpi-clang + mpi: OpenMPI 4.1 (MPI 3.1 floor) + mpi_packages: libopenmpi-dev openmpi-bin + capability_profile: mpi3-floor + - compiler: GNU + cc: gcc + cxx: g++ + preset: ci-mpi-gcc + mpi: MPICH 4 (MPI-4 capability) + mpi_packages: libmpich-dev mpich + capability_profile: mpi4 + - compiler: Clang + cc: clang + cxx: clang++ + preset: ci-mpi-clang + mpi: MPICH 4 (MPI-4 capability) + mpi_packages: libmpich-dev mpich + capability_profile: mpi4 + env: + CC: ${{ matrix.cc }} + CXX: ${{ matrix.cxx }} + VCPKG_ROOT: ${{ runner.temp }}/vcpkg steps: - - name: Install dependencies - run: sudo apt-get install -y libopenmpi-dev pybind11-dev - - uses: actions/checkout@v5 - with: - submodules: 'recursive' - - name: Export compiler - run: | - echo CXX=${{ matrix.compiler.cxx }} >> $GITHUB_ENV - echo CC=${{ matrix.compiler.cc }} >> $GITHUB_ENV - - name: Build - run: ./compile_withcmake.sh ${{ matrix.argument }} - - name: Test Python interface - if: matrix.argument == 'BUILDPYTHONMODULE' - run: python3 ./deploy/callkahipfrompython.py + - uses: actions/checkout@v5 + with: + submodules: recursive + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + ninja-build pkg-config python3-venv \ + gcc g++ clang \ + ${{ matrix.mpi_packages }} + + - name: Install tested CMake 4.x + run: | + python3 -m venv "$RUNNER_TEMP/cmake" + "$RUNNER_TEMP/cmake/bin/python" -m pip install \ + --disable-pip-version-check "cmake==$CMAKE_VERSION" + echo "$RUNNER_TEMP/cmake/bin" >> "$GITHUB_PATH" + + - name: Verify the required systemd memory scope + run: | + ci/run-limited true + ci/run-limited bash -c \ + '[[ "$(cmake --version | head -n 1)" == "cmake version ${CMAKE_VERSION}" ]]' + + - name: Bootstrap manifest-pinned vcpkg + run: ci/run-limited ci/bootstrap-vcpkg.sh + + - name: Configure + run: ci/run-limited cmake --preset "${{ matrix.preset }}" + + - name: Verify MPI feature profile + run: | + ci/run-limited cmake \ + "-DBUILD_DIR=$GITHUB_WORKSPACE/out/build/${{ matrix.preset }}" \ + "-DPROFILE=${{ matrix.capability_profile }}" \ + -P ci/verify-mpi-capabilities.cmake + + - name: Build (at most two jobs) + run: ci/run-limited cmake --build --preset "build-${{ matrix.preset }}" + + - name: Run strict tests except large and performance + run: ci/run-limited ctest --preset "test-${{ matrix.preset }}" + + - name: Verify staged install and pkg-config consumers + run: | + ci/run-limited ctest \ + --test-dir "out/build/${{ matrix.preset }}" \ + --output-on-failure --stop-on-failure --no-tests=error \ + -L install -LE "(large|performance)" diff --git a/.github/workflows/build_nompi.yml b/.github/workflows/build_nompi.yml index 8566799c..45ec465a 100644 --- a/.github/workflows/build_nompi.yml +++ b/.github/workflows/build_nompi.yml @@ -1,4 +1,4 @@ -name: C/C++ CI without OpenMPI +name: Linux serial and sanitizer CI on: push: @@ -11,29 +11,91 @@ on: env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + CMAKE_VERSION: "4.3.4" + VCPKG_BASELINE: "3f7b5a12ef0a55e7b59339b2b69cac4b56d6dbf9" + VCPKG_MAX_CONCURRENCY: "2" + CMAKE_BUILD_PARALLEL_LEVEL: "2" + CTEST_PARALLEL_LEVEL: "2" jobs: - build: - runs-on: ubuntu-latest - continue-on-error: true + serial: + name: ${{ matrix.name }} + runs-on: ubuntu-24.04 + timeout-minutes: 90 strategy: + fail-fast: false matrix: - compiler: - - { name: GNU, cc: gcc, cxx: g++ } - timeout-minutes: 60 + include: + - name: GNU serial release + cc: gcc + cxx: g++ + preset: ci-serial-gcc + asan_options: "" + ubsan_options: "" + - name: GNU serial 64-bit package release + cc: gcc + cxx: g++ + preset: ci-serial-gcc-64bit + asan_options: "" + ubsan_options: "" + - name: Clang serial release + cc: clang + cxx: clang++ + preset: ci-serial-clang + asan_options: "" + ubsan_options: "" + - name: Clang ASan and UBSan + cc: clang + cxx: clang++ + preset: ci-sanitizer-clang + asan_options: detect_leaks=1:halt_on_error=1 + ubsan_options: print_stacktrace=1:halt_on_error=1 + env: + CC: ${{ matrix.cc }} + CXX: ${{ matrix.cxx }} + VCPKG_ROOT: ${{ runner.temp }}/vcpkg + ASAN_OPTIONS: ${{ matrix.asan_options }} + UBSAN_OPTIONS: ${{ matrix.ubsan_options }} steps: - - name: Install dependencies - run: sudo apt-get install -y libopenmpi-dev pybind11-dev - - uses: actions/checkout@v5 - with: - submodules: 'recursive' - - name: Export compiler - run: | - echo CXX=${{ matrix.compiler.cxx }} >> $GITHUB_ENV - echo CC=${{ matrix.compiler.cc }} >> $GITHUB_ENV - - name: Build - run: ./compile_withcmake.sh -DNOMPI=On - - name: Test Python interface - if: matrix.argument == 'BUILDPYTHONMODULE' - run: python3 ./deploy/callkahipfrompython.py + - uses: actions/checkout@v5 + with: + submodules: recursive + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + ninja-build pkg-config python3-venv \ + gcc g++ clang + + - name: Install tested CMake 4.x + run: | + python3 -m venv "$RUNNER_TEMP/cmake" + "$RUNNER_TEMP/cmake/bin/python" -m pip install \ + --disable-pip-version-check "cmake==$CMAKE_VERSION" + echo "$RUNNER_TEMP/cmake/bin" >> "$GITHUB_PATH" + + - name: Verify the required systemd memory scope + run: | + ci/run-limited true + ci/run-limited bash -c \ + '[[ "$(cmake --version | head -n 1)" == "cmake version ${CMAKE_VERSION}" ]]' + + - name: Bootstrap manifest-pinned vcpkg + run: ci/run-limited ci/bootstrap-vcpkg.sh + + - name: Configure + run: ci/run-limited cmake --preset "${{ matrix.preset }}" + + - name: Build (at most two jobs) + run: ci/run-limited cmake --build --preset "build-${{ matrix.preset }}" + + - name: Run strict tests except large and performance + run: ci/run-limited ctest --preset "test-${{ matrix.preset }}" + + - name: Verify staged install and pkg-config consumers + run: | + ci/run-limited ctest \ + --test-dir "out/build/${{ matrix.preset }}" \ + --output-on-failure --stop-on-failure --no-tests=error \ + -L install -LE "(large|performance)" diff --git a/.github/workflows/build_windows.yml b/.github/workflows/build_windows.yml index 7b944d75..bde5de36 100644 --- a/.github/workflows/build_windows.yml +++ b/.github/workflows/build_windows.yml @@ -1,4 +1,4 @@ -name: Windows CI +name: Windows serial CI on: push: @@ -11,24 +11,62 @@ on: env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + CMAKE_VERSION: "4.3.4" + VCPKG_BASELINE: "3f7b5a12ef0a55e7b59339b2b69cac4b56d6dbf9" + VCPKG_MAX_CONCURRENCY: "2" + CMAKE_BUILD_PARALLEL_LEVEL: "2" + CTEST_PARALLEL_LEVEL: "2" jobs: - build: - runs-on: windows-latest - continue-on-error: true - timeout-minutes: 60 + serial: + runs-on: windows-2022 + timeout-minutes: 90 + env: + VCPKG_ROOT: ${{ runner.temp }}/vcpkg steps: - - uses: actions/checkout@v5 - with: - submodules: 'recursive' - - name: Configure - run: | - mkdir build - cd build - cmake .. -DCMAKE_BUILD_TYPE=Release -DNOMPI=On - - name: Build - run: cmake --build build --config Release - - name: Test interface - run: | - cd build/Release - .\interface_test.exe + - uses: actions/checkout@v5 + with: + submodules: recursive + + - uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 + + - name: Install Ninja and pkg-config + run: choco install --no-progress -y ninja pkgconfiglite + + - name: Install tested CMake 4.x + shell: pwsh + run: | + py -3 -m venv "$env:RUNNER_TEMP/cmake" + & "$env:RUNNER_TEMP/cmake/Scripts/python.exe" -m pip install ` + --disable-pip-version-check "cmake==$env:CMAKE_VERSION" + Add-Content $env:GITHUB_PATH "$env:RUNNER_TEMP/cmake/Scripts" + + - name: Bootstrap manifest-pinned vcpkg + shell: pwsh + run: ./ci/bootstrap-vcpkg.ps1 + + # Windows is the documented exception: systemd user scopes do not exist. + - name: Verify CMake version (Windows systemd exception) + shell: pwsh + run: | + $actual = cmake --version | Select-Object -First 1 + if ($actual -ne "cmake version $env:CMAKE_VERSION") { + throw "Expected CMake $env:CMAKE_VERSION, found '$actual'" + } + + - name: Configure (Windows systemd exception) + run: cmake --preset ci-windows-serial + + - name: Build with at most two jobs (Windows systemd exception) + run: cmake --build --preset build-ci-windows-serial + + - name: Run strict tests except large and performance (Windows systemd exception) + run: ctest --preset test-ci-windows-serial + + - name: Verify staged install and pkg-config consumers (Windows systemd exception) + run: >- + ctest --test-dir out/build/ci-windows-serial + --output-on-failure --stop-on-failure --no-tests=error + -L install -LE "(large|performance)" diff --git a/.gitignore b/.gitignore index f8240950..048eb7eb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,11 @@ .cache/ build/ deploy/ +.idea +**/.DS_Store +cmake-build-* +/out/ +/CMakeUserPresets.json +/vcpkg_installed/ +/.devenv* +/devenv.local.nix diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..6faa7b24 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "extern/caliper"] + path = extern/caliper + url = https://github.com/LLNL/Caliper.git diff --git a/CMakeLists.txt b/CMakeLists.txt index b580d0aa..dcfef943 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,414 +1,860 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 4.0...4.3) include(CheckCXXCompilerFlag) -include(GNUInstallDirs) - -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -find_program(CCACHE_PROGRAM ccache) -list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") - -if(CCACHE_PROGRAM) - message(STATUS "Using compiler cache") - set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}") - set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${CCACHE_PROGRAM}") -endif() project(KaHIP VERSION 3.24 LANGUAGES C CXX) +include(CTest) +include(GNUInstallDirs) -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -set(CMAKE_POSITION_INDEPENDENT_CODE ON) - -# if no build mode is specified build in release mode -if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - set(CMAKE_BUILD_TYPE "Release") -endif() - -# --march=nativeflag -option(NONATIVEOPTIMIZATIONS "Disable --march=native optimizations" OFF) - -# tweak compiler flags -CHECK_CXX_COMPILER_FLAG(-funroll-loops COMPILER_SUPPORTS_FUNROLL_LOOPS) -if(COMPILER_SUPPORTS_FUNROLL_LOOPS) - add_definitions(-funroll-loops) -endif() -CHECK_CXX_COMPILER_FLAG(-fno-stack-limit COMPILER_SUPPORTS_FNOSTACKLIMITS) -if(COMPILER_SUPPORTS_FNOSTACKLIMITS) - add_definitions(-fno-stack-limit) -endif() -if(NOT MSVC) - CHECK_CXX_COMPILER_FLAG(-Wall COMPILER_SUPPORTS_WALL) - if(COMPILER_SUPPORTS_WALL) - add_definitions(-Wall) - endif() - CHECK_CXX_COMPILER_FLAG(-march=native COMPILER_SUPPORTS_MARCH_NATIVE) - if(COMPILER_SUPPORTS_MARCH_NATIVE) - if( NOT NONATIVEOPTIMIZATIONS ) - add_definitions(-march=native) +foreach( + install_directory_variable + IN ITEMS CMAKE_INSTALL_BINDIR CMAKE_INSTALL_LIBDIR CMAKE_INSTALL_INCLUDEDIR +) + if(IS_ABSOLUTE "${${install_directory_variable}}") + message( + FATAL_ERROR + "${install_directory_variable} must be relative so KaHIP installs and pkg-config metadata remain relocatable" + ) endif() - endif() - CHECK_CXX_COMPILER_FLAG(-fpermissive COMPILER_SUPPORTS_FPERMISSIVE) - if(COMPILER_SUPPORTS_FPERMISSIVE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpermissive") - endif() - CHECK_CXX_COMPILER_FLAG(-Wno-unused-result COMPILER_SUPPORTS_UNUSED) - if(COMPILER_SUPPORTS_UNUSED) - add_definitions(-Wno-unused-result) - endif() -endif() -CHECK_CXX_COMPILER_FLAG(-Wno-sign-compare COMPILER_SUPPORTS_NOSIGNCOMP) -if(COMPILER_SUPPORTS_NOSIGNCOMP) - add_definitions(-Wno-sign-compare) -endif() +endforeach() +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") +include(KaHIPSettings) +include(KahipPkgConfig) # Check dependencies find_package(OpenMP) if(OpenMP_CXX_FOUND) - message(STATUS "OpenMP support detected") - add_definitions(${OpenMP_CXX_FLAGS}) + message(STATUS "OpenMP support detected") else() - message(WARNING "OpenMP not available, activating workaround") - add_library(OpenMP::OpenMP_CXX IMPORTED INTERFACE) - set_property(TARGET OpenMP::OpenMP_CXX PROPERTY INTERFACE_COMPILE_OPTIONS "") - include_directories(${CMAKE_CURRENT_SOURCE_DIR}/misc) + message(WARNING "OpenMP not available, activating workaround") + add_library(OpenMP::OpenMP_CXX IMPORTED INTERFACE) + set_property( + TARGET OpenMP::OpenMP_CXX + PROPERTY INTERFACE_COMPILE_OPTIONS "" + ) + target_include_directories( + OpenMP::OpenMP_CXX + INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/misc + ) endif() -find_library(LIB_METIS metis) -if(LIB_METIS) - message(STATUS "Metis support detected") - find_library(LIB_GK GKlib) - if (NOT LIB_GK) - message(STATUS "Metis requires GKlib, but GKlib was not found") - set(LIB_METIS "NOTFOUND") - else() - add_definitions("-DUSEMETIS") - if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - include_directories(/usr/local/include) +find_package(metis CONFIG) +if(metis_FOUND) + message(STATUS "Metis support detected") + find_library(GKlib CONFIG) + if(NOT GKlib_FOUND) + message(STATUS "Metis requires GKlib, but GKlib was not found") + set(metis_FOUND OFF) + else() + target_compile_definitions(kahip_options INTERFACE USEMETIS) endif() - endif() endif() # Windows compatibility if(WIN32) - add_definitions(-DNOMINMAX) - # MSVC: use /W3 instead of /Wall (which is extremely pedantic) - string(REGEX REPLACE "/W[0-4]" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") - add_compile_options(/W3) + target_compile_definitions(kahip_options INTERFACE NOMINMAX) endif() # 64 Bit option option(64BITMODE "64 bit mode" OFF) if(64BITMODE) - add_definitions("-DMODE64BITEDGES") - add_definitions("-DKAHIP_64BIT") - add_definitions("-DPOINTER64=1") + target_compile_definitions( + kahip_options + INTERFACE MODE64BITEDGES KAHIP_64BIT POINTER64=1 + ) endif() # optimized output option(OPTIMIZED_OUTPUT "optimized output" OFF) if(OPTIMIZED_OUTPUT) - add_definitions("-DKAFFPAOUTPUT") + target_compile_definitions(kahip_options INTERFACE KAFFPAOUTPUT) endif() - # Optionally disable all MPI-dependent targets option(NOMPI "disable all targets that depend on MPI (kaffpaE, ParHIP)" OFF) # ParHIP option(PARHIP "build ParHIP" ON) option(DETERMINISTIC_PARHIP "enforce deterministic computations in ParHIP" OFF) +option(KAHIP_ENABLE_MPI_TRACE "enable canonical ParHIP MPI stage tracing" OFF) # Look for MPI (needed for ParHIP and kaffpaE) # Report which MPI we actually found, # may need use MPI_HOME hint to get the proper one (eg, on openSUSE) if(NOT NOMPI) - # Always look for MPI since kaffpaE also requires it - find_package(MPI REQUIRED) + add_library(kahip_fatal_diagnostics INTERFACE) + target_compile_features(kahip_fatal_diagnostics INTERFACE cxx_std_23) + target_sources( + kahip_fatal_diagnostics + INTERFACE + FILE_SET fatal_diagnostics_headers + TYPE HEADERS + BASE_DIRS "${PROJECT_SOURCE_DIR}/lib" + FILES "${PROJECT_SOURCE_DIR}/lib/tools/fatal_diagnostics.h" + ) + target_include_directories( + kahip_fatal_diagnostics + INTERFACE "$" + ) - if(${MPI_C_FOUND}) - message("MPI detected (can use MPI_HOME hint to direct the detection...)") - message(STATUS "MPI include: ${MPI_C_INCLUDE_DIRS}") - message(STATUS "MPI library: ${MPI_C_LIBRARIES}") - endif() + # Always look for MPI since kaffpaE also requires it + find_package(MPI 3.1 REQUIRED COMPONENTS C CXX) + include(CheckCXXSourceCompiles) + include(CMakePushCheckState) + cmake_push_check_state(RESET) + set(CMAKE_REQUIRED_LIBRARIES MPI::MPI_CXX) + check_cxx_source_compiles( + [=[ + #include + int main() { + return MPI_Bcast_c( + nullptr, MPI_Count{0}, MPI_BYTE, 0, MPI_COMM_WORLD + ); + } + ]=] + KAHIP_HAVE_MPI_BCAST_C + ) + cmake_pop_check_state() + target_compile_definitions( + kahip_options + INTERFACE + KAHIP_HAVE_MPI_BCAST_C=$ + ) - if(PARHIP) - message(STATUS "ParHIP build requested") - else() - message(STATUS "ParHIP build disabled") - endif() + if(MPI_C_FOUND) + message( + "MPI detected (can use MPI_HOME hint to direct the detection...)" + ) + message(STATUS "MPI include: ${MPI_C_INCLUDE_DIRS}") + message(STATUS "MPI library: ${MPI_C_LIBRARIES}") + endif() + + if(PARHIP) + message(STATUS "ParHIP build requested") + else() + message(STATUS "ParHIP build disabled") + endif() else() - message(STATUS "Build without MPI dependency: kaffpaE and ParHIP disabled") + message(STATUS "Build without MPI dependency: kaffpaE and ParHIP disabled") endif() - # tcmalloc option(USE_TCMALLOC "if available, link against tcmalloc" OFF) - # ILP improver option(USE_ILP "build local ILP improver - introduces dependency on Gurobi" OFF) +# Argtable3 +add_subdirectory(extern/argtable3-3.2.2) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/app) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/extern/argtable3-3.2.2) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/lib) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/lib/io) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/lib/partition) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/lib/tools) +add_library(kahip_version INTERFACE) +target_sources( + kahip_version + INTERFACE + FILE_SET HEADERS + TYPE HEADERS + BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/lib/version" + FILES "${CMAKE_CURRENT_SOURCE_DIR}/lib/version/version.h" +) + +set( + KAHIP_ROOT_HEADER_BASE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/app + ${CMAKE_CURRENT_SOURCE_DIR}/lib + ${CMAKE_CURRENT_SOURCE_DIR}/lib/io + ${CMAKE_CURRENT_SOURCE_DIR}/lib/partition + ${CMAKE_CURRENT_SOURCE_DIR}/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement + ${CMAKE_CURRENT_SOURCE_DIR}/lib/tools + ${CMAKE_CURRENT_SOURCE_DIR}/lib/version +) + +function(kahip_add_private_header_set target base_dir) + target_sources( + ${target} + PRIVATE + FILE_SET private_headers + TYPE HEADERS + BASE_DIRS "${base_dir}" + FILES ${ARGN} + ) +endfunction() + +function(kahip_add_header_root_file_sets target prefix) + # KaHIP's legacy flat includes require several nested search roots. CMake + # forbids nested BASE_DIRS within one file set, so each build-only root is + # represented by its own empty HEADERS set. The headers themselves remain + # declared once in the target's private catch-all set. These compatibility + # sets are internal usage requirements and must not be installed or exported. + set(root_index 0) + foreach(base_dir IN LISTS ARGN) + target_sources( + ${target} + PUBLIC + FILE_SET "${prefix}_${root_index}" + TYPE HEADERS + BASE_DIRS "${base_dir}" + ) + math(EXPR root_index "${root_index} + 1") + endforeach() +endfunction() + +function(kahip_configure_root_object target) + kahip_add_header_root_file_sets( + ${target} + root_header_root + ${KAHIP_ROOT_HEADER_BASE_DIRS} + ) + target_link_libraries( + ${target} + PUBLIC kahip_options + PRIVATE kahip_warnings + ) +endfunction() + +file( + GLOB_RECURSE KAHIP_CORE_HEADERS + CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/app/*.h" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/*.h" +) +list( + FILTER KAHIP_CORE_HEADERS + EXCLUDE + REGEX "/lib/(mapping|node_ordering|parallel_mh|spac)/" +) +file( + GLOB_RECURSE KAHIP_COLLECTIVE_HEADERS + CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/lib/parallel_mh/*.h" +) +file( + GLOB_RECURSE KAHIP_MAPPING_HEADERS + CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/lib/mapping/*.h" +) +file( + GLOB_RECURSE KAHIP_SPAC_HEADERS + CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/lib/spac/*.h" +) +file( + GLOB_RECURSE KAHIP_ORDERING_HEADERS + CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/lib/node_ordering/*.h" +) set(LIBKAFFPA_SOURCE_FILES - lib/data_structure/graph_hierarchy.cpp - lib/algorithms/strongly_connected_components.cpp - lib/algorithms/topological_sort.cpp - lib/algorithms/push_relabel.cpp - lib/io/graph_io.cpp - lib/tools/quality_metrics.cpp - lib/tools/random_functions.cpp - lib/tools/graph_extractor.cpp - lib/tools/misc.cpp - lib/tools/partition_snapshooter.cpp - lib/partition/graph_partitioner.cpp - lib/partition/w_cycles/wcycle_partitioner.cpp - lib/partition/coarsening/coarsening.cpp - lib/partition/coarsening/contraction.cpp - lib/partition/coarsening/edge_rating/edge_ratings.cpp - lib/partition/coarsening/matching/matching.cpp - lib/partition/coarsening/matching/random_matching.cpp - lib/partition/coarsening/matching/gpa/path.cpp - lib/partition/coarsening/matching/gpa/gpa_matching.cpp - lib/partition/coarsening/matching/gpa/path_set.cpp - lib/partition/coarsening/clustering/node_ordering.cpp - lib/partition/coarsening/clustering/size_constraint_label_propagation.cpp - lib/partition/initial_partitioning/initial_partitioning.cpp - lib/partition/initial_partitioning/initial_partitioner.cpp - lib/partition/initial_partitioning/initial_partition_bipartition.cpp - lib/partition/initial_partitioning/initial_refinement/initial_refinement.cpp - lib/partition/initial_partitioning/bipartition.cpp - lib/partition/initial_partitioning/initial_node_separator.cpp - lib/partition/uncoarsening/uncoarsening.cpp - lib/partition/uncoarsening/separator/area_bfs.cpp - lib/partition/uncoarsening/separator/vertex_separator_algorithm.cpp - lib/partition/uncoarsening/separator/vertex_separator_flow_solver.cpp - lib/partition/uncoarsening/refinement/cycle_improvements/greedy_neg_cycle.cpp - lib/partition/uncoarsening/refinement/cycle_improvements/problem_factory.cpp - lib/partition/uncoarsening/refinement/cycle_improvements/augmented_Qgraph.cpp - lib/partition/uncoarsening/refinement/mixed_refinement.cpp - lib/partition/uncoarsening/refinement/label_propagation_refinement/label_propagation_refinement.cpp - lib/partition/uncoarsening/refinement/refinement.cpp - lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/two_way_fm.cpp - lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/two_way_flow_refinement.cpp - lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/boundary_bfs.cpp - lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/cut_flow_problem_solver.cpp - lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/most_balanced_minimum_cuts/most_balanced_minimum_cuts.cpp - lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_refinement.cpp - lib/partition/uncoarsening/refinement/quotient_graph_refinement/complete_boundary.cpp - lib/partition/uncoarsening/refinement/quotient_graph_refinement/partial_boundary.cpp - lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/quotient_graph_scheduling.cpp - lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/simple_quotient_graph_scheduler.cpp - lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/active_block_quotient_graph_scheduler.cpp - lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement.cpp - lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_core.cpp - lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_commons.cpp - lib/partition/uncoarsening/refinement/cycle_improvements/augmented_Qgraph_fabric.cpp - lib/partition/uncoarsening/refinement/cycle_improvements/advanced_models.cpp - lib/partition/uncoarsening/refinement/kway_graph_refinement/multitry_kway_fm.cpp - lib/partition/uncoarsening/refinement/node_separators/greedy_ns_local_search.cpp - lib/partition/uncoarsening/refinement/node_separators/fm_ns_local_search.cpp - lib/partition/uncoarsening/refinement/node_separators/localized_fm_ns_local_search.cpp - lib/algorithms/cycle_search.cpp - lib/partition/uncoarsening/refinement/cycle_improvements/cycle_refinement.cpp - lib/partition/uncoarsening/refinement/tabu_search/tabu_search.cpp - extern/argtable3-3.2.2/argtable3.c) -add_library(libkaffpa OBJECT ${LIBKAFFPA_SOURCE_FILES}) + lib/data_structure/graph_hierarchy.cpp + lib/algorithms/strongly_connected_components.cpp + lib/algorithms/topological_sort.cpp + lib/algorithms/push_relabel.cpp + lib/io/graph_io.cpp + lib/tools/quality_metrics.cpp + lib/tools/random_functions.cpp + lib/tools/graph_extractor.cpp + lib/tools/misc.cpp + lib/tools/partition_snapshooter.cpp + lib/partition/graph_partitioner.cpp + lib/partition/w_cycles/wcycle_partitioner.cpp + lib/partition/coarsening/coarsening.cpp + lib/partition/coarsening/contraction.cpp + lib/partition/coarsening/edge_rating/edge_ratings.cpp + lib/partition/coarsening/matching/matching.cpp + lib/partition/coarsening/matching/random_matching.cpp + lib/partition/coarsening/matching/gpa/path.cpp + lib/partition/coarsening/matching/gpa/gpa_matching.cpp + lib/partition/coarsening/matching/gpa/path_set.cpp + lib/partition/coarsening/clustering/node_ordering.cpp + lib/partition/coarsening/clustering/size_constraint_label_propagation.cpp + lib/partition/initial_partitioning/initial_partitioning.cpp + lib/partition/initial_partitioning/initial_partitioner.cpp + lib/partition/initial_partitioning/initial_partition_bipartition.cpp + lib/partition/initial_partitioning/initial_refinement/initial_refinement.cpp + lib/partition/initial_partitioning/bipartition.cpp + lib/partition/initial_partitioning/initial_node_separator.cpp + lib/partition/uncoarsening/uncoarsening.cpp + lib/partition/uncoarsening/separator/area_bfs.cpp + lib/partition/uncoarsening/separator/vertex_separator_algorithm.cpp + lib/partition/uncoarsening/separator/vertex_separator_flow_solver.cpp + lib/partition/uncoarsening/refinement/cycle_improvements/greedy_neg_cycle.cpp + lib/partition/uncoarsening/refinement/cycle_improvements/problem_factory.cpp + lib/partition/uncoarsening/refinement/cycle_improvements/augmented_Qgraph.cpp + lib/partition/uncoarsening/refinement/mixed_refinement.cpp + lib/partition/uncoarsening/refinement/label_propagation_refinement/label_propagation_refinement.cpp + lib/partition/uncoarsening/refinement/refinement.cpp + lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/two_way_fm.cpp + lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/two_way_flow_refinement.cpp + lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/boundary_bfs.cpp + lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/cut_flow_problem_solver.cpp + lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/most_balanced_minimum_cuts/most_balanced_minimum_cuts.cpp + lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_refinement.cpp + lib/partition/uncoarsening/refinement/quotient_graph_refinement/complete_boundary.cpp + lib/partition/uncoarsening/refinement/quotient_graph_refinement/partial_boundary.cpp + lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/quotient_graph_scheduling.cpp + lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/simple_quotient_graph_scheduler.cpp + lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/active_block_quotient_graph_scheduler.cpp + lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement.cpp + lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_core.cpp + lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_commons.cpp + lib/partition/uncoarsening/refinement/cycle_improvements/augmented_Qgraph_fabric.cpp + lib/partition/uncoarsening/refinement/cycle_improvements/advanced_models.cpp + lib/partition/uncoarsening/refinement/kway_graph_refinement/multitry_kway_fm.cpp + lib/partition/uncoarsening/refinement/node_separators/greedy_ns_local_search.cpp + lib/partition/uncoarsening/refinement/node_separators/fm_ns_local_search.cpp + lib/partition/uncoarsening/refinement/node_separators/localized_fm_ns_local_search.cpp + lib/algorithms/cycle_search.cpp + lib/partition/uncoarsening/refinement/cycle_improvements/cycle_refinement.cpp + lib/partition/uncoarsening/refinement/tabu_search/tabu_search.cpp +) +add_library(kahip_core_obj OBJECT ${LIBKAFFPA_SOURCE_FILES}) +kahip_configure_root_object(kahip_core_obj) +kahip_add_private_header_set( + kahip_core_obj + "${CMAKE_CURRENT_SOURCE_DIR}" + ${KAHIP_CORE_HEADERS} +) +target_link_libraries( + kahip_core_obj + PUBLIC kahip_version + PRIVATE argtable3 +) if(NOT NOMPI) - set(LIBKAFFPA_PARALLEL_SOURCE_FILES - lib/parallel_mh/parallel_mh_async.cpp - lib/parallel_mh/population.cpp - lib/parallel_mh/galinier_combine/gal_combine.cpp - lib/parallel_mh/galinier_combine/construct_partition.cpp - lib/parallel_mh/exchange/exchanger.cpp - lib/tools/graph_communication.cpp - lib/tools/mpi_tools.cpp) - add_library(libkaffpa_parallel OBJECT ${LIBKAFFPA_PARALLEL_SOURCE_FILES}) - target_include_directories(libkaffpa_parallel PUBLIC ${MPI_CXX_INCLUDE_PATH}) + set(LIBKAFFPA_PARALLEL_SOURCE_FILES + lib/parallel_mh/parallel_mh_async.cpp + lib/parallel_mh/population.cpp + lib/parallel_mh/galinier_combine/gal_combine.cpp + lib/parallel_mh/galinier_combine/construct_partition.cpp + lib/parallel_mh/exchange/exchanger.cpp + ) + add_library( + kahip_collective_obj + OBJECT + ${LIBKAFFPA_PARALLEL_SOURCE_FILES} + ) + kahip_configure_root_object(kahip_collective_obj) + kahip_add_private_header_set( + kahip_collective_obj + "${CMAKE_CURRENT_SOURCE_DIR}" + ${KAHIP_COLLECTIVE_HEADERS} + ) + target_link_libraries( + kahip_collective_obj + PUBLIC MPI::MPI_CXX + PRIVATE kahip_fatal_diagnostics + ) endif() set(LIBMAPPING_SOURCE_FILES - lib/mapping/local_search_mapping.cpp - lib/mapping/full_search_space.cpp - lib/mapping/full_search_space_pruned.cpp - lib/mapping/communication_graph_search_space.cpp - lib/mapping/fast_construct_mapping.cpp - lib/mapping/construct_distance_matrix.cpp - lib/mapping/mapping_algorithms.cpp - lib/mapping/construct_mapping.cpp) -add_library(libmapping OBJECT ${LIBMAPPING_SOURCE_FILES}) + lib/mapping/local_search_mapping.cpp + lib/mapping/full_search_space.cpp + lib/mapping/full_search_space_pruned.cpp + lib/mapping/communication_graph_search_space.cpp + lib/mapping/fast_construct_mapping.cpp + lib/mapping/construct_distance_matrix.cpp + lib/mapping/mapping_algorithms.cpp + lib/mapping/construct_mapping.cpp +) +add_library(kahip_mapping_obj OBJECT ${LIBMAPPING_SOURCE_FILES}) +kahip_configure_root_object(kahip_mapping_obj) +kahip_add_private_header_set( + kahip_mapping_obj + "${CMAKE_CURRENT_SOURCE_DIR}" + ${KAHIP_MAPPING_HEADERS} +) set(LIBSPAC_SOURCE_FILES lib/spac/spac.cpp) -add_library(libspac OBJECT ${LIBSPAC_SOURCE_FILES}) +add_library(kahip_spac_obj OBJECT ${LIBSPAC_SOURCE_FILES}) +kahip_configure_root_object(kahip_spac_obj) +kahip_add_private_header_set( + kahip_spac_obj + "${CMAKE_CURRENT_SOURCE_DIR}" + ${KAHIP_SPAC_HEADERS} +) set(NODE_ORDERING_SOURCE_FILES - lib/node_ordering/min_degree_ordering.cpp - lib/node_ordering/nested_dissection.cpp - lib/node_ordering/ordering_tools.cpp - lib/node_ordering/reductions.cpp) -add_library(libnodeordering OBJECT ${NODE_ORDERING_SOURCE_FILES}) + lib/node_ordering/min_degree_ordering.cpp + lib/node_ordering/nested_dissection.cpp + lib/node_ordering/ordering_tools.cpp + lib/node_ordering/reductions.cpp +) +add_library(kahip_ordering_obj OBJECT ${NODE_ORDERING_SOURCE_FILES}) +kahip_configure_root_object(kahip_ordering_obj) +kahip_add_private_header_set( + kahip_ordering_obj + "${CMAKE_CURRENT_SOURCE_DIR}" + ${KAHIP_ORDERING_HEADERS} +) # generate targets for each binary -add_executable(kaffpa app/kaffpa.cpp $ $) -target_compile_definitions(kaffpa PRIVATE "-DMODE_KAFFPA") -target_link_libraries(kaffpa ${OpenMP_CXX_LIBRARIES}) -install(TARGETS kaffpa DESTINATION bin) -if (USE_TCMALLOC) - find_library(TCMALLOC_LIB tcmalloc) - if (TCMALLOC_LIB) - target_link_libraries(kaffpa ${TCMALLOC_LIB}) - message(STATUS "Using tcmalloc: ${TCMALLOC_LIB}") - else () - message(STATUS "tcmalloc enabled but unavailable on this system") - endif () -endif () +add_executable(kaffpa app/kaffpa.cpp) +target_link_libraries(kaffpa PRIVATE kahip_core_obj kahip_mapping_obj) +target_compile_definitions(kaffpa PRIVATE MODE_KAFFPA) +target_link_libraries(kaffpa PRIVATE OpenMP::OpenMP_CXX) +target_link_libraries( + kaffpa + PRIVATE kahip_options kahip_warnings argtable3 +) +install(TARGETS kaffpa RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") +if(USE_TCMALLOC) + find_library(TCMALLOC_LIB tcmalloc) + if(TCMALLOC_LIB) + target_link_libraries(kaffpa PRIVATE ${TCMALLOC_LIB}) + message(STATUS "Using tcmalloc: ${TCMALLOC_LIB}") + else() + message(STATUS "tcmalloc enabled but unavailable on this system") + endif() +endif() -add_executable(global_multisection app/global_multisection.cpp $ $) -target_compile_definitions(global_multisection PRIVATE "-DMODE_KAFFPA" "-DMODE_GLOBALMS") -target_link_libraries(global_multisection ${OpenMP_CXX_LIBRARIES}) -install(TARGETS global_multisection DESTINATION bin) -if (USE_TCMALLOC) - find_library(TCMALLOC_LIB tcmalloc) - if (TCMALLOC_LIB) - target_link_libraries(global_multisection ${TCMALLOC_LIB}) - message(STATUS "Using tcmalloc: ${TCMALLOC_LIB}") - else () - message(STATUS "tcmalloc enabled but unavailable on this system") - endif () -endif () +add_executable(global_multisection app/global_multisection.cpp) +target_link_libraries(global_multisection PRIVATE kahip_core_obj kahip_mapping_obj) +target_compile_definitions( + global_multisection + PRIVATE MODE_KAFFPA MODE_GLOBALMS +) +target_link_libraries(global_multisection PRIVATE OpenMP::OpenMP_CXX) +target_link_libraries( + global_multisection + PRIVATE kahip_options kahip_warnings argtable3 +) +install( + TARGETS global_multisection + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" +) +if(USE_TCMALLOC) + find_library(TCMALLOC_LIB tcmalloc) + if(TCMALLOC_LIB) + target_link_libraries(global_multisection PRIVATE ${TCMALLOC_LIB}) + message(STATUS "Using tcmalloc: ${TCMALLOC_LIB}") + else() + message(STATUS "tcmalloc enabled but unavailable on this system") + endif() +endif() + +add_executable(evaluator app/evaluator.cpp) +target_link_libraries(evaluator PRIVATE kahip_core_obj kahip_mapping_obj) +target_compile_definitions(evaluator PRIVATE MODE_EVALUATOR) +target_link_libraries(evaluator PRIVATE OpenMP::OpenMP_CXX) +target_link_libraries( + evaluator + PRIVATE kahip_options kahip_warnings argtable3 +) +install(TARGETS evaluator RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") + +add_executable(edge_evaluator app/edge_evaluator.cpp) +target_link_libraries(edge_evaluator PRIVATE kahip_core_obj kahip_mapping_obj) +target_compile_definitions(edge_evaluator PRIVATE MODE_EVALUATOR) +target_link_libraries(edge_evaluator PRIVATE OpenMP::OpenMP_CXX) +target_link_libraries( + edge_evaluator + PRIVATE kahip_options kahip_warnings +) +target_link_libraries(edge_evaluator PRIVATE argtable3) +install( + TARGETS edge_evaluator + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" +) + +add_executable(node_separator app/node_separator_ml.cpp) +target_link_libraries(node_separator PRIVATE kahip_core_obj kahip_mapping_obj) +target_compile_definitions(node_separator PRIVATE MODE_NODESEP) +target_link_libraries(node_separator PRIVATE OpenMP::OpenMP_CXX) +target_link_libraries( + node_separator + PRIVATE kahip_options kahip_warnings argtable3 +) +install( + TARGETS node_separator + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" +) + +add_executable(label_propagation app/label_propagation.cpp) +target_link_libraries(label_propagation PRIVATE kahip_core_obj kahip_mapping_obj) +target_compile_definitions(label_propagation PRIVATE MODE_LABELPROPAGATION) +target_link_libraries(label_propagation PRIVATE OpenMP::OpenMP_CXX) +target_link_libraries( + label_propagation + PRIVATE kahip_options kahip_warnings argtable3 +) +install( + TARGETS label_propagation + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" +) -add_executable(evaluator app/evaluator.cpp $ $) -target_compile_definitions(evaluator PRIVATE "-DMODE_EVALUATOR") -target_link_libraries(evaluator ${OpenMP_CXX_LIBRARIES}) -install(TARGETS evaluator DESTINATION bin) - -add_executable(edge_evaluator app/edge_evaluator.cpp $ $) -target_compile_definitions(edge_evaluator PRIVATE "-DMODE_EVALUATOR") -target_link_libraries(edge_evaluator ${OpenMP_CXX_LIBRARIES}) -install(TARGETS edge_evaluator DESTINATION bin) - -add_executable(node_separator app/node_separator_ml.cpp $ $) -target_compile_definitions(node_separator PRIVATE "-DMODE_NODESEP") -target_link_libraries(node_separator ${OpenMP_CXX_LIBRARIES}) -install(TARGETS node_separator DESTINATION bin) - -add_executable(label_propagation app/label_propagation.cpp $ $) -target_compile_definitions(label_propagation PRIVATE "-DMODE_LABELPROPAGATION") -target_link_libraries(label_propagation ${OpenMP_CXX_LIBRARIES}) -install(TARGETS label_propagation DESTINATION bin) - -add_executable(partition_to_vertex_separator app/partition_to_vertex_separator.cpp $ $) -target_compile_definitions(partition_to_vertex_separator PRIVATE "-DMODE_PARTITIONTOVERTEXSEPARATOR") -target_link_libraries(partition_to_vertex_separator ${OpenMP_CXX_LIBRARIES}) -install(TARGETS partition_to_vertex_separator DESTINATION bin) - -add_executable(interface_test misc/example_library_call/interface_test.cpp interface/kaHIP_interface.cpp $ $ $ $) -target_include_directories(interface_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/interface) -target_compile_definitions(interface_test PRIVATE "-DMODE_KAFFPA") -target_link_libraries(interface_test ${OpenMP_CXX_LIBRARIES}) -if(LIB_METIS) - target_link_libraries(interface_test ${LIB_METIS} ${LIB_GK}) +add_executable( + partition_to_vertex_separator + app/partition_to_vertex_separator.cpp +) +target_link_libraries( + partition_to_vertex_separator + PRIVATE kahip_core_obj kahip_mapping_obj +) +target_compile_definitions( + partition_to_vertex_separator + PRIVATE MODE_PARTITIONTOVERTEXSEPARATOR +) +target_link_libraries(partition_to_vertex_separator PRIVATE OpenMP::OpenMP_CXX) +target_link_libraries( + partition_to_vertex_separator + PRIVATE kahip_options kahip_warnings argtable3 +) +install( + TARGETS partition_to_vertex_separator + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" +) + +add_executable( + interface_test + misc/example_library_call/interface_test.cpp + interface/kaHIP_interface.cpp +) +target_link_libraries( + interface_test + PRIVATE kahip_core_obj kahip_mapping_obj kahip_ordering_obj kahip_spac_obj +) +kahip_add_private_header_set( + interface_test + "${CMAKE_CURRENT_SOURCE_DIR}/interface" + "${CMAKE_CURRENT_SOURCE_DIR}/interface/kaHIP_interface.h" +) +target_compile_definitions(interface_test PRIVATE MODE_KAFFPA) +target_link_libraries(interface_test PRIVATE OpenMP::OpenMP_CXX) +target_link_libraries( + interface_test + PRIVATE kahip_options kahip_warnings argtable3 +) +if(metis_FOUND) + target_link_libraries(interface_test PRIVATE metis GKlib) +endif() +install(TARGETS interface_test RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") +if(BUILD_TESTING) + add_executable( + serial_c_boundary_failure_probe + tests/interface/serial_c_boundary_failure_probe.cpp + ) + target_link_libraries( + serial_c_boundary_failure_probe + PRIVATE + kahip_static + kahip_options + kahip_warnings + ) + add_test( + NAME unit-serial-kaffpa-c-boundary-failure + COMMAND + "${CMAKE_COMMAND}" + "-DPROBE=$" + "-DMODE=allocation" + "-DEXPECTED_DIAGNOSTIC=KaHIP serial C boundary kaffpa:" + "-DEXPECTED_INJECTION=armed serial allocation failure" + "-DEXPECTED_MARKER=observed serial C boundary abort after restoring std::cout" + -P + "${PROJECT_SOURCE_DIR}/cmake/tests/VerifySerialCBoundaryFailure.cmake" + ) + set_tests_properties( + unit-serial-kaffpa-c-boundary-failure + PROPERTIES + RUN_SERIAL TRUE + TIMEOUT 10 + LABELS "unit;interface;serial;failure" + ) + add_test( + NAME cmake-object-architecture + COMMAND + ${CMAKE_COMMAND} + -DKAHIP_SOURCE_DIR=${PROJECT_SOURCE_DIR} + -P ${PROJECT_SOURCE_DIR}/cmake/tests/VerifyObjectArchitecture.cmake + ) + set_tests_properties( + cmake-object-architecture + PROPERTIES LABELS "cmake;architecture" + ) + add_test( + NAME cmake-mpi-capability-profiles + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_DIR=${PROJECT_SOURCE_DIR}" + "-DTEST_ROOT=${PROJECT_BINARY_DIR}/test-mpi-capabilities" + -P + "${PROJECT_SOURCE_DIR}/ci/test-verify-mpi-capabilities.cmake" + ) + set_tests_properties( + cmake-mpi-capability-profiles + PROPERTIES LABELS "cmake;mpi" + ) + if(UNIX) + find_program(KAHIP_TEST_BASH_EXECUTABLE NAMES bash REQUIRED) + add_test( + NAME unit-cirrus-scale-runners + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_DIR=${PROJECT_SOURCE_DIR}" + "-DBASH_EXECUTABLE=${KAHIP_TEST_BASH_EXECUTABLE}" + "-DTEST_ROOT=${PROJECT_BINARY_DIR}/test-cirrus-scale-runners" + -P + "${PROJECT_SOURCE_DIR}/ci/cirrus/VerifyScaleRunners.cmake" + ) + set_tests_properties( + unit-cirrus-scale-runners + PROPERTIES + TIMEOUT 30 + LABELS "unit;script;cirrus;scale" + ) + endif() + add_test(NAME integration-kahip-interface COMMAND interface_test) + set_tests_properties( + integration-kahip-interface + PROPERTIES LABELS "integration;serial" + ) endif() -install(TARGETS interface_test DESTINATION bin) if(NOT NOMPI) - add_executable(kaffpaE app/kaffpaE.cpp $ $ $) - target_compile_definitions(kaffpaE PRIVATE "-DMODE_KAFFPAE") - target_include_directories(kaffpaE PUBLIC ${MPI_CXX_INCLUDE_PATH}) - target_link_libraries(kaffpaE ${MPI_CXX_LIBRARIES} ${OpenMP_CXX_LIBRARIES} OpenMP::OpenMP_CXX ) - install(TARGETS kaffpaE DESTINATION bin) + add_executable( + kaffpaE + app/kaffpaE.cpp + app/mpi_application_runtime.cpp + ) + target_link_libraries( + kaffpaE + PRIVATE kahip_core_obj kahip_mapping_obj kahip_collective_obj + ) + target_compile_definitions(kaffpaE PRIVATE MODE_KAFFPAE) + target_link_libraries( + kaffpaE + PRIVATE + MPI::MPI_CXX + OpenMP::OpenMP_CXX + kahip_fatal_diagnostics + ) + target_link_libraries( + kaffpaE + PRIVATE kahip_options kahip_warnings argtable3 + ) + install(TARGETS kaffpaE RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") endif() -add_executable(graphchecker app/graphchecker.cpp $ $) -target_compile_definitions(graphchecker PRIVATE "-DMODE_GRAPHCHECKER") -target_link_libraries(graphchecker ${OpenMP_CXX_LIBRARIES}) -install(TARGETS graphchecker DESTINATION bin) - -add_executable(edge_partitioning app/spac.cpp $ $ $) -target_compile_definitions(edge_partitioning PRIVATE "-DMODE_KAFFPA") -target_link_libraries(edge_partitioning ${OpenMP_CXX_LIBRARIES}) -install(TARGETS edge_partitioning DESTINATION bin) - -add_executable(node_ordering app/node_ordering.cpp $ $) -target_compile_definitions(node_ordering PRIVATE "-DMODE_NODESEP" "-DMODE_NODEORDERING") -target_link_libraries(node_ordering ${OpenMP_CXX_LIBRARIES}) -install(TARGETS node_ordering DESTINATION bin) - -if(LIB_METIS) - add_executable(fast_node_ordering app/fast_node_ordering.cpp $ $) - target_compile_definitions(fast_node_ordering PRIVATE "-DMODE_NODESEP" "-DMODE_NODEORDERING" "-DFASTORDERING") - target_link_libraries(fast_node_ordering ${OpenMP_CXX_LIBRARIES} ${LIB_METIS} ${LIB_GK}) - install(TARGETS fast_node_ordering DESTINATION bin) -endif() +add_executable(graphchecker app/graphchecker.cpp) +target_link_libraries(graphchecker PRIVATE kahip_core_obj kahip_mapping_obj) +target_compile_definitions(graphchecker PRIVATE MODE_GRAPHCHECKER) +target_link_libraries(graphchecker PRIVATE OpenMP::OpenMP_CXX) +target_link_libraries( + graphchecker + PRIVATE kahip_options kahip_warnings argtable3 +) +install(TARGETS graphchecker RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") + +add_executable(edge_partitioning app/spac.cpp) +target_link_libraries(edge_partitioning PRIVATE kahip_core_obj kahip_mapping_obj kahip_spac_obj) +target_compile_definitions(edge_partitioning PRIVATE MODE_KAFFPA) +target_link_libraries(edge_partitioning PRIVATE OpenMP::OpenMP_CXX) +target_link_libraries( + edge_partitioning + PRIVATE kahip_options kahip_warnings argtable3 +) +install( + TARGETS edge_partitioning + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" +) +add_executable(node_ordering app/node_ordering.cpp) +target_link_libraries(node_ordering PRIVATE kahip_core_obj kahip_ordering_obj) +target_compile_definitions( + node_ordering + PRIVATE MODE_NODESEP MODE_NODEORDERING +) +target_link_libraries(node_ordering PRIVATE OpenMP::OpenMP_CXX) +target_link_libraries( + node_ordering + PRIVATE kahip_options kahip_warnings argtable3 +) +install(TARGETS node_ordering RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") + +if(metis_FOUND) + add_executable(fast_node_ordering app/fast_node_ordering.cpp) + target_link_libraries( + fast_node_ordering + PRIVATE kahip_core_obj kahip_mapping_obj kahip_ordering_obj + ) + target_compile_definitions( + fast_node_ordering + PRIVATE MODE_NODESEP MODE_NODEORDERING FASTORDERING + ) + target_link_libraries( + fast_node_ordering + PRIVATE OpenMP::OpenMP_CXX metis GKlib + ) + target_link_libraries( + fast_node_ordering + PRIVATE kahip_options kahip_warnings argtable3 + ) + install( + TARGETS fast_node_ordering + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + ) +endif() # Shared interface library -add_library(kahip SHARED interface/kaHIP_interface.cpp $ - $ - $ - $) -target_include_directories(kahip PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/interface) -target_compile_definitions(kahip PRIVATE "-DMODE_KAFFPA") -target_link_libraries(kahip PUBLIC ${OpenMP_CXX_LIBRARIES}) -if(LIB_METIS) - target_link_libraries(kahip PUBLIC ${LIB_METIS} ${LIB_GK}) +add_library(kahip SHARED interface/kaHIP_interface.cpp) +set_target_properties(kahip PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON) +target_sources( + kahip + PUBLIC + FILE_SET public_headers + TYPE HEADERS + BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/interface" + FILES "${CMAKE_CURRENT_SOURCE_DIR}/interface/kaHIP_interface.h" +) +target_link_libraries( + kahip + PRIVATE kahip_core_obj kahip_mapping_obj kahip_ordering_obj kahip_spac_obj +) +target_compile_definitions( + kahip + PUBLIC + $<$:KAHIP_64BIT> + $<$:USEMETIS> + PRIVATE MODE_KAFFPA +) +target_link_libraries(kahip PRIVATE OpenMP::OpenMP_CXX) +target_link_libraries( + kahip + PRIVATE kahip_options kahip_warnings argtable3 +) +if(metis_FOUND) + target_link_libraries(kahip PRIVATE metis GKlib) endif() -install(TARGETS kahip DESTINATION lib) +install( + TARGETS kahip + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + FILE_SET public_headers DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" +) # pkg-config +set(KAHIP_INSTALL_PKGCONFIGDIR "${CMAKE_INSTALL_LIBDIR}/pkgconfig") +set(KAHIP_PKGCONFIG_PREFIX "${CMAKE_INSTALL_PREFIX}") +cmake_path( + RELATIVE_PATH KAHIP_PKGCONFIG_PREFIX + BASE_DIRECTORY "${CMAKE_INSTALL_FULL_LIBDIR}/pkgconfig" + OUTPUT_VARIABLE KAHIP_PKGCONFIG_PREFIX_FROM_PCFILEDIR +) +set(KAHIP_PKGCONFIG_API_OPTIONS "") +if(64BITMODE) + list(APPEND KAHIP_PKGCONFIG_API_OPTIONS 64BIT) +endif() +if(metis_FOUND) + list(APPEND KAHIP_PKGCONFIG_API_OPTIONS METIS) +endif() +kahip_format_serial_api_pkg_config_cflags( + KAHIP_PKGCONFIG_CFLAGS + ${KAHIP_PKGCONFIG_API_OPTIONS} +) configure_file("lib/kahip.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/kahip.pc" @ONLY) -install(FILES "${CMAKE_CURRENT_BINARY_DIR}/kahip.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") +install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/kahip.pc" + DESTINATION "${KAHIP_INSTALL_PKGCONFIGDIR}" +) # Static interface library -add_library(kahip_static interface/kaHIP_interface.cpp $ - $ - $ - $) -target_include_directories(kahip_static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/interface) -target_compile_definitions(kahip_static PRIVATE "-DMODE_KAFFPA") -target_link_libraries(kahip_static PUBLIC ${OpenMP_CXX_LIBRARIES}) -set_target_properties(kahip_static PROPERTIES PUBLIC_HEADER interface/kaHIP_interface.h) - -if(LIB_METIS) - target_link_libraries(kahip_static PUBLIC ${LIB_METIS} ${LIB_GK}) +add_library(kahip_static STATIC interface/kaHIP_interface.cpp) +target_sources( + kahip_static + PUBLIC + FILE_SET public_headers + TYPE HEADERS + BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/interface" + FILES "${CMAKE_CURRENT_SOURCE_DIR}/interface/kaHIP_interface.h" +) +target_link_libraries( + kahip_static + PRIVATE kahip_core_obj kahip_mapping_obj kahip_ordering_obj kahip_spac_obj +) +target_compile_definitions( + kahip_static + PUBLIC + $<$:KAHIP_64BIT> + $<$:USEMETIS> + PRIVATE MODE_KAFFPA +) +target_link_libraries(kahip_static PRIVATE OpenMP::OpenMP_CXX) +target_link_libraries( + kahip_static + PRIVATE kahip_options kahip_warnings argtable3 +) +if(metis_FOUND) + target_link_libraries(kahip_static PRIVATE metis GKlib) endif() -install(TARGETS kahip_static - ARCHIVE DESTINATION lib - LIBRARY DESTINATION lib - PUBLIC_HEADER DESTINATION include +install( + TARGETS kahip_static + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" +) + +if(BUILD_TESTING) + foreach(serial_library IN ITEMS kahip kahip_static) + set(consumer_target "${serial_library}_build_tree_abi_consumer") + add_executable( + ${consumer_target} + tests/interface/kahip_build_tree_abi_consumer.c + ) + target_compile_features(${consumer_target} PRIVATE c_std_17) + set_target_properties( + ${consumer_target} + PROPERTIES LINKER_LANGUAGE CXX + ) + target_link_libraries(${consumer_target} PRIVATE ${serial_library}) + target_link_options( + ${consumer_target} + PRIVATE + $> ) + add_test( + NAME "integration-${serial_library}-build-tree-abi" + COMMAND ${consumer_target} + ) + set_tests_properties( + "integration-${serial_library}-build-tree-abi" + PROPERTIES LABELS "integration;interface;serial;abi" + ) + endforeach() +endif() # ParHIP if(NOT NOMPI AND PARHIP) - add_subdirectory(parallel/modified_kahip) - add_subdirectory(parallel/parallel_src) + if(BUILD_TESTING) + find_package(Catch2 3 CONFIG REQUIRED) + endif() + + add_subdirectory(parallel/modified_kahip) + add_subdirectory(parallel/parallel_src) endif() if(USE_ILP) - find_package(Gurobi REQUIRED) - MESSAGE("Using Gurobi for ILP solver in ilp_improve") - add_executable(ilp_improve app/ilp_improve.cpp $ $) - target_include_directories(ilp_improve PUBLIC ${GUROBI_INCLUDE_DIR}) - target_compile_definitions(ilp_improve PRIVATE "-DMODE_ILPIMPROVE") - target_link_libraries(ilp_improve ${OpenMP_CXX_LIBRARIES} ${GUROBI_LIBRARIES}) - - add_executable(ilp_exact app/ilp_exact.cpp $ $) - target_include_directories(ilp_exact PUBLIC ${GUROBI_INCLUDE_DIR}) - target_compile_definitions(ilp_exact PRIVATE "-DMODE_ILPIMPROVE" "-DMODE_ILPEXACT") - target_link_libraries(ilp_exact ${OpenMP_CXX_LIBRARIES} ${GUROBI_LIBRARIES}) + find_package(Gurobi REQUIRED) + message("Using Gurobi for ILP solver in ilp_improve") + add_executable(ilp_improve app/ilp_improve.cpp) + target_link_libraries(ilp_improve PRIVATE kahip_core_obj kahip_mapping_obj) + target_include_directories(ilp_improve PRIVATE ${GUROBI_INCLUDE_DIR}) + target_compile_definitions(ilp_improve PRIVATE MODE_ILPIMPROVE) + target_link_libraries( + ilp_improve + PRIVATE OpenMP::OpenMP_CXX ${GUROBI_LIBRARIES} + ) + add_executable(ilp_exact app/ilp_exact.cpp) + target_link_libraries(ilp_exact PRIVATE kahip_core_obj kahip_mapping_obj) + target_include_directories(ilp_exact PRIVATE ${GUROBI_INCLUDE_DIR}) + target_compile_definitions( + ilp_exact + PRIVATE MODE_ILPIMPROVE MODE_ILPEXACT + ) + target_link_libraries( + ilp_exact + PRIVATE OpenMP::OpenMP_CXX ${GUROBI_LIBRARIES} + ) endif() # pybind11 module @@ -427,5 +873,188 @@ if (BUILDPYTHONMODULE) endif() target_link_libraries(kahip_python_binding PUBLIC kahip_static) + target_link_libraries(kahip_python_binding PRIVATE kahip_options) set_target_properties(kahip_python_binding PROPERTIES OUTPUT_NAME "kahip") endif () + +if(BUILD_TESTING) + find_package(PkgConfig REQUIRED) + include( + "${PROJECT_SOURCE_DIR}/cmake/KahipInstallConsumerArguments.cmake" + ) + add_test( + NAME pkg-config-mpi-flags + COMMAND + "${CMAKE_COMMAND}" + "-DKAHIP_SOURCE_DIR=${PROJECT_SOURCE_DIR}" + "-DPKG_CONFIG_EXECUTABLE=${PKG_CONFIG_EXECUTABLE}" + "-DWORK_DIRECTORY=${PROJECT_BINARY_DIR}/test-install/pkgconfig-flags" + -P + "${PROJECT_SOURCE_DIR}/cmake/tests/VerifyMpiPkgConfigFlags.cmake" + ) + set_tests_properties(pkg-config-mpi-flags PROPERTIES LABELS install) + + add_test( + NAME pkg-config-kahip-abi + COMMAND + "${CMAKE_COMMAND}" + "-DKAHIP_SOURCE_DIR=${PROJECT_SOURCE_DIR}" + "-DPKG_CONFIG_EXECUTABLE=${PKG_CONFIG_EXECUTABLE}" + "-DWORK_DIRECTORY=${PROJECT_BINARY_DIR}/test-install/pkgconfig-abi" + -P + "${PROJECT_SOURCE_DIR}/cmake/tests/VerifyKahipPkgConfigAbi.cmake" + ) + add_test( + NAME install-consumer-argument-forwarding + COMMAND + "${CMAKE_COMMAND}" + "-DKAHIP_SOURCE_DIR=${PROJECT_SOURCE_DIR}" + "-DWORK_DIRECTORY=${PROJECT_BINARY_DIR}/test-install/consumer-arguments" + -P + "${PROJECT_SOURCE_DIR}/cmake/tests/VerifyInstallConsumerArguments.cmake" + ) + set_tests_properties( + pkg-config-kahip-abi + install-consumer-argument-forwarding + PROPERTIES LABELS install + ) + + set(KAHIP_INSTALL_TEST_WITH_PARHIP OFF) + set(KAHIP_INSTALL_TEST_PARHIP_EXECUTABLE "") + set(KAHIP_INSTALL_TEST_PARHIP_SHARED_LIBRARY "") + set(KAHIP_INSTALL_TEST_PARHIP_LINKER_LIBRARY "") + set(KAHIP_INSTALL_TEST_PARHIP_STATIC_LIBRARY "") + if(TARGET parhip_interface) + set(KAHIP_INSTALL_TEST_WITH_PARHIP ON) + set( + KAHIP_INSTALL_TEST_PARHIP_SHARED_LIBRARY + "$" + ) + set( + KAHIP_INSTALL_TEST_PARHIP_LINKER_LIBRARY + "$" + ) + set( + KAHIP_INSTALL_TEST_PARHIP_STATIC_LIBRARY + "$" + ) + endif() + if(TARGET parhip) + set( + KAHIP_INSTALL_TEST_PARHIP_EXECUTABLE + "$" + ) + endif() + + set(KAHIP_INSTALL_TEST_TARGET_WINDOWS OFF) + if(WIN32) + set(KAHIP_INSTALL_TEST_TARGET_WINDOWS ON) + endif() + set(KAHIP_INSTALL_TEST_TARGET_UNIX OFF) + if(UNIX) + set(KAHIP_INSTALL_TEST_TARGET_UNIX ON) + endif() + set(KAHIP_INSTALL_TEST_CONTEXT_ARGUMENTS "") + set( + KAHIP_INSTALL_TEST_COMPILE_OPTIONS + "$>, >" + ) + set( + KAHIP_INSTALL_TEST_LINK_OPTIONS + "$>, >" + ) + kahip_append_consumer_cache_argument( + KAHIP_INSTALL_TEST_CONTEXT_ARGUMENTS + TARGET_WINDOWS + "${KAHIP_INSTALL_TEST_TARGET_WINDOWS}" + ) + kahip_append_consumer_cache_argument( + KAHIP_INSTALL_TEST_CONTEXT_ARGUMENTS + TARGET_UNIX + "${KAHIP_INSTALL_TEST_TARGET_UNIX}" + ) + kahip_append_consumer_cache_argument( + KAHIP_INSTALL_TEST_CONTEXT_ARGUMENTS + KAHIP_64BIT + "${64BITMODE}" + ) + kahip_append_consumer_cache_argument( + KAHIP_INSTALL_TEST_CONTEXT_ARGUMENTS + CMAKE_C_FLAGS + "${CMAKE_C_FLAGS} ${KAHIP_INSTALL_TEST_COMPILE_OPTIONS}" + ) + kahip_append_consumer_cache_argument( + KAHIP_INSTALL_TEST_CONTEXT_ARGUMENTS + CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} ${KAHIP_INSTALL_TEST_COMPILE_OPTIONS}" + ) + kahip_append_consumer_cache_argument( + KAHIP_INSTALL_TEST_CONTEXT_ARGUMENTS + CMAKE_EXE_LINKER_FLAGS + "${CMAKE_EXE_LINKER_FLAGS} ${KAHIP_INSTALL_TEST_LINK_OPTIONS}" + ) + foreach( + context_variable + IN ITEMS + CMAKE_POSITION_INDEPENDENT_CODE + CMAKE_OSX_ARCHITECTURES + CMAKE_OSX_SYSROOT + CMAKE_OSX_DEPLOYMENT_TARGET + VCPKG_INSTALLED_DIR + VCPKG_TARGET_TRIPLET + ) + if( + DEFINED ${context_variable} + AND NOT "${${context_variable}}" STREQUAL "" + ) + kahip_append_consumer_cache_argument( + KAHIP_INSTALL_TEST_CONTEXT_ARGUMENTS + "${context_variable}" + "${${context_variable}}" + ) + endif() + endforeach() + + add_test( + NAME install-pkg-config-consumer + COMMAND + "${CMAKE_COMMAND}" + "-DPROJECT_BINARY_DIR=${PROJECT_BINARY_DIR}" + "-DKAHIP_SOURCE_DIR=${PROJECT_SOURCE_DIR}" + "-DSTAGE_PREFIX=${PROJECT_BINARY_DIR}/test-install/pkgconfig-prefix" + "-DCONSUMER_SOURCE_DIR=${PROJECT_SOURCE_DIR}/cmake/pkgconfig-consumer" + "-DCONSUMER_BINARY_DIR=${PROJECT_BINARY_DIR}/test-install/pkgconfig-consumer" + "-DPKG_CONFIG_EXECUTABLE=${PKG_CONFIG_EXECUTABLE}" + "-DCTEST_COMMAND=${CMAKE_CTEST_COMMAND}" + "-DCONSUMER_GENERATOR=${CMAKE_GENERATOR}" + "-DCONSUMER_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM}" + "-DCONSUMER_GENERATOR_TOOLSET=${CMAKE_GENERATOR_TOOLSET}" + "-DCONSUMER_GENERATOR_INSTANCE=${CMAKE_GENERATOR_INSTANCE}" + "-DC_COMPILER=${CMAKE_C_COMPILER}" + "-DCXX_COMPILER=${CMAKE_CXX_COMPILER}" + "-DNM_EXECUTABLE=${CMAKE_NM}" + "-DTOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" + "-DBUILD_CONFIG=$" + "-DINSTALL_BINDIR=${CMAKE_INSTALL_BINDIR}" + "-DINSTALL_LIBDIR=${CMAKE_INSTALL_LIBDIR}" + "-DINSTALL_INCLUDEDIR=${CMAKE_INSTALL_INCLUDEDIR}" + "-DKAHIP_SHARED_LIBRARY=$" + "-DKAHIP_LINKER_LIBRARY=$" + "-DKAHIP_STATIC_LIBRARY=$" + "-DWITH_PARHIP=${KAHIP_INSTALL_TEST_WITH_PARHIP}" + "-DPARHIP_EXECUTABLE=${KAHIP_INSTALL_TEST_PARHIP_EXECUTABLE}" + "-DPARHIP_SHARED_LIBRARY=${KAHIP_INSTALL_TEST_PARHIP_SHARED_LIBRARY}" + "-DPARHIP_LINKER_LIBRARY=${KAHIP_INSTALL_TEST_PARHIP_LINKER_LIBRARY}" + "-DPARHIP_STATIC_LIBRARY=${KAHIP_INSTALL_TEST_PARHIP_STATIC_LIBRARY}" + ${KAHIP_INSTALL_TEST_CONTEXT_ARGUMENTS} + -P + "${PROJECT_SOURCE_DIR}/cmake/VerifyPkgConfigInstall.cmake" + ) + set_tests_properties( + install-pkg-config-consumer + PROPERTIES + LABELS install + RUN_SERIAL TRUE + TIMEOUT 180 + ) +endif() diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 00000000..a2f5ce6d --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,470 @@ +{ + "version": 9, + "cmakeMinimumRequired": { + "major": 4, + "minor": 0, + "patch": 0 + }, + "configurePresets": [ + { + "name": "conf-common", + "description": "General settings that apply to all configurations", + "hidden": true, + "binaryDir": "${sourceDir}/out/build/${presetName}", + "installDir": "${sourceDir}/out/install/${presetName}", + "cacheVariables": { + "BUILD_TESTING": "ON" + } + }, + { + "name": "ci-vcpkg-common", + "description": "CI-only settings for using vcpkg", + "hidden": true, + "toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" + }, + { + "name": "conf-unix-common", + "description": "Unix-like settings shared by GCC and Clang", + "hidden": true, + "inherits": "conf-common", + "generator": "Ninja", + "condition": { + "type": "inList", + "string": "${hostSystemName}", + "list": [ + "Linux", + "Darwin" + ] + } + }, + { + "name": "gcc-common", + "description": "General settings for GCC configurations", + "hidden": true, + "inherits": "conf-unix-common", + "cacheVariables": { + "CMAKE_C_COMPILER": "gcc", + "CMAKE_CXX_COMPILER": "g++" + } + }, + { + "name": "clang-common", + "description": "General settings for Clang configurations", + "hidden": true, + "inherits": "conf-unix-common", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++" + } + }, + { + "name": "unix-gcc-debug", + "displayName": "GCC Debug", + "description": "Build KaHIP for a Unix-like system with GCC in Debug mode", + "inherits": "gcc-common", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "unix-gcc-release", + "displayName": "GCC Release", + "description": "Build KaHIP for a Unix-like system with GCC in Release mode", + "inherits": "gcc-common", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "unix-clang-debug", + "displayName": "Clang Debug", + "description": "Build KaHIP for a Unix-like system with Clang in Debug mode", + "inherits": "clang-common", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "unix-clang-release", + "displayName": "Clang Release", + "description": "Build KaHIP for a Unix-like system with Clang in Release mode", + "inherits": "clang-common", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "cirrus-common", + "description": "Cirrus settings for the Cray compiler wrappers and system MPI", + "hidden": true, + "inherits": "conf-common", + "generator": "Unix Makefiles", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + }, + "cacheVariables": { + "BUILD_TESTING": "OFF", + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_C_COMPILER": "cc", + "CMAKE_CXX_COMPILER": "CC", + "NOMPI": "OFF", + "PARHIP": "ON", + "DETERMINISTIC_PARHIP": "ON", + "NONATIVEOPTIMIZATIONS": "ON" + } + }, + { + "name": "cirrus-gnu-release", + "displayName": "Cirrus GCC 14 release", + "description": "Build ParHIP after switching Cirrus to PrgEnv-gnu/8.6.0", + "inherits": "cirrus-common" + }, + { + "name": "cirrus-cray-release", + "displayName": "Cirrus Cray Clang 19 release", + "description": "Build ParHIP in the default Cirrus PrgEnv-cray/8.6.0 environment", + "inherits": "cirrus-common" + }, + { + "name": "cirrus-test-common", + "description": "Cirrus test settings using Slurm srun as the MPI launcher", + "hidden": true, + "inherits": "cirrus-common", + "cacheVariables": { + "BUILD_TESTING": "ON", + "MPIEXEC_EXECUTABLE": { + "type": "FILEPATH", + "value": "srun" + }, + "MPIEXEC_NUMPROC_FLAG": { + "type": "STRING", + "value": "-n" + }, + "MPIEXEC_PREFLAGS": { + "type": "STRING", + "value": "--hint=nomultithread;--distribution=block:block;--kill-on-bad-exit;--unbuffered" + }, + "MPIEXEC_POSTFLAGS": { + "type": "STRING", + "value": "" + } + } + }, + { + "name": "cirrus-gnu-tests", + "displayName": "Cirrus GCC 14 tests", + "description": "Build and register ParHIP tests after loading Cirrus ccs/gnu-2026-06", + "inherits": "cirrus-test-common" + }, + { + "name": "cirrus-cray-tests", + "displayName": "Cirrus Cray Clang 19 tests", + "description": "Build and register ParHIP tests in the Cirrus PrgEnv-cray/8.6.0 environment", + "inherits": "cirrus-test-common" + }, + { + "name": "ci-serial-gcc", + "displayName": "CI serial GCC", + "description": "Reproducible GCC release build without MPI", + "inherits": [ + "gcc-common", + "ci-vcpkg-common" + ], + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "NOMPI": "ON", + "PARHIP": "OFF", + "NONATIVEOPTIMIZATIONS": "ON" + } + }, + { + "name": "ci-serial-gcc-64bit", + "displayName": "CI serial GCC 64-bit", + "description": "Reproducible GCC release build with the 64-bit KaHIP API", + "inherits": "ci-serial-gcc", + "cacheVariables": { + "64BITMODE": "ON" + } + }, + { + "name": "ci-serial-clang", + "displayName": "CI serial Clang", + "description": "Reproducible Clang release build without MPI", + "inherits": [ + "clang-common", + "ci-vcpkg-common" + ], + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "NOMPI": "ON", + "PARHIP": "OFF", + "NONATIVEOPTIMIZATIONS": "ON" + } + }, + { + "name": "ci-mpi-gcc", + "displayName": "CI MPI GCC", + "description": "Deterministic ParHIP release build with GCC", + "inherits": [ + "gcc-common", + "ci-vcpkg-common" + ], + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "NOMPI": "OFF", + "PARHIP": "ON", + "DETERMINISTIC_PARHIP": "ON", + "KAHIP_ENABLE_MPI_TRACE": "ON", + "NONATIVEOPTIMIZATIONS": "ON" + } + }, + { + "name": "ci-mpi-clang", + "displayName": "CI MPI Clang", + "description": "Deterministic ParHIP release build with Clang", + "inherits": [ + "clang-common", + "ci-vcpkg-common" + ], + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "NOMPI": "OFF", + "PARHIP": "ON", + "DETERMINISTIC_PARHIP": "ON", + "KAHIP_ENABLE_MPI_TRACE": "ON", + "NONATIVEOPTIMIZATIONS": "ON" + } + }, + { + "name": "ci-sanitizer-clang", + "displayName": "CI Clang ASan and UBSan", + "description": "Debug serial build with address and undefined-behaviour sanitizers", + "inherits": [ + "clang-common", + "ci-vcpkg-common" + ], + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "NOMPI": "ON", + "PARHIP": "OFF", + "NONATIVEOPTIMIZATIONS": "ON", + "kahip_ENABLE_IPO": "OFF", + "kahip_ENABLE_SANITIZERS": "ON", + "kahip_ENABLE_SANITIZER_ADDRESS": "ON", + "kahip_ENABLE_SANITIZER_UNDEFINED": "ON", + "kahip_ENABLE_SANITIZER_LEAK": "OFF", + "kahip_ENABLE_SANITIZER_THREAD": "OFF", + "kahip_ENABLE_SANITIZER_MEMORY": "OFF" + } + }, + { + "name": "ci-windows-serial", + "displayName": "CI Windows serial", + "description": "Reproducible MSVC release build without MPI", + "inherits": [ + "conf-common", + "ci-vcpkg-common" + ], + "generator": "Ninja", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "NOMPI": "ON", + "PARHIP": "OFF", + "NONATIVEOPTIMIZATIONS": "ON", + "VCPKG_TARGET_TRIPLET": "x64-windows" + } + } + ], + "buildPresets": [ + { + "name": "build-unix-gcc-debug", + "description": "Build the GCC Debug configuration", + "configurePreset": "unix-gcc-debug" + }, + { + "name": "build-unix-gcc-release", + "description": "Build the GCC Release configuration", + "configurePreset": "unix-gcc-release" + }, + { + "name": "build-unix-clang-debug", + "description": "Build the Clang Debug configuration", + "configurePreset": "unix-clang-debug" + }, + { + "name": "build-unix-clang-release", + "description": "Build the Clang Release configuration", + "configurePreset": "unix-clang-release" + }, + { + "name": "build-cirrus-gnu-release", + "description": "Build the Cirrus GCC 14 configuration with at most two jobs", + "configurePreset": "cirrus-gnu-release", + "jobs": 2 + }, + { + "name": "build-cirrus-cray-release", + "description": "Build the Cirrus Cray Clang 19 configuration with at most two jobs", + "configurePreset": "cirrus-cray-release", + "jobs": 2 + }, + { + "name": "build-cirrus-gnu-tests", + "description": "Build the Cirrus GCC 14 test configuration with at most two jobs", + "configurePreset": "cirrus-gnu-tests", + "jobs": 2 + }, + { + "name": "build-cirrus-cray-tests", + "description": "Build the Cirrus Cray Clang 19 test configuration with at most two jobs", + "configurePreset": "cirrus-cray-tests", + "jobs": 2 + }, + { + "name": "build-ci-serial-gcc", + "description": "Build the CI serial GCC configuration with at most two jobs", + "configurePreset": "ci-serial-gcc", + "jobs": 2 + }, + { + "name": "build-ci-serial-gcc-64bit", + "description": "Build the CI serial GCC 64-bit configuration with at most two jobs", + "configurePreset": "ci-serial-gcc-64bit", + "jobs": 2 + }, + { + "name": "build-ci-serial-clang", + "description": "Build the CI serial Clang configuration with at most two jobs", + "configurePreset": "ci-serial-clang", + "jobs": 2 + }, + { + "name": "build-ci-mpi-gcc", + "description": "Build the CI MPI GCC configuration with at most two jobs", + "configurePreset": "ci-mpi-gcc", + "jobs": 2 + }, + { + "name": "build-ci-mpi-clang", + "description": "Build the CI MPI Clang configuration with at most two jobs", + "configurePreset": "ci-mpi-clang", + "jobs": 2 + }, + { + "name": "build-ci-sanitizer-clang", + "description": "Build the CI sanitizer configuration with at most two jobs", + "configurePreset": "ci-sanitizer-clang", + "jobs": 2 + }, + { + "name": "build-ci-windows-serial", + "description": "Build the CI Windows serial configuration with at most two jobs", + "configurePreset": "ci-windows-serial", + "jobs": 2 + } + ], + "testPresets": [ + { + "name": "test-common", + "description": "Strict CTest settings shared by all configurations", + "hidden": true, + "output": { + "outputOnFailure": true + }, + "filter": { + "exclude": { + "label": "(large|performance)" + } + }, + "execution": { + "noTestsAction": "error", + "stopOnFailure": true + } + }, + { + "name": "test-unix-gcc-debug", + "displayName": "GCC Debug tests (strict)", + "inherits": "test-common", + "configurePreset": "unix-gcc-debug" + }, + { + "name": "test-unix-gcc-release", + "displayName": "GCC Release tests (strict)", + "inherits": "test-common", + "configurePreset": "unix-gcc-release" + }, + { + "name": "test-unix-clang-debug", + "displayName": "Clang Debug tests (strict)", + "inherits": "test-common", + "configurePreset": "unix-clang-debug" + }, + { + "name": "test-unix-clang-release", + "displayName": "Clang Release tests (strict)", + "inherits": "test-common", + "configurePreset": "unix-clang-release" + }, + { + "name": "test-cirrus-gnu-tests", + "displayName": "Cirrus GCC 14 tests (strict)", + "inherits": "test-common", + "configurePreset": "cirrus-gnu-tests" + }, + { + "name": "test-cirrus-cray-tests", + "displayName": "Cirrus Cray Clang 19 tests (strict)", + "inherits": "test-common", + "configurePreset": "cirrus-cray-tests" + }, + { + "name": "test-ci-serial-gcc", + "displayName": "CI serial GCC tests (strict)", + "inherits": "test-common", + "configurePreset": "ci-serial-gcc" + }, + { + "name": "test-ci-serial-gcc-64bit", + "displayName": "CI serial GCC 64-bit tests (strict)", + "inherits": "test-common", + "configurePreset": "ci-serial-gcc-64bit" + }, + { + "name": "test-ci-serial-clang", + "displayName": "CI serial Clang tests (strict)", + "inherits": "test-common", + "configurePreset": "ci-serial-clang" + }, + { + "name": "test-ci-mpi-gcc", + "displayName": "CI MPI GCC tests (strict)", + "inherits": "test-common", + "configurePreset": "ci-mpi-gcc" + }, + { + "name": "test-ci-mpi-clang", + "displayName": "CI MPI Clang tests (strict)", + "inherits": "test-common", + "configurePreset": "ci-mpi-clang" + }, + { + "name": "test-ci-sanitizer-clang", + "displayName": "CI Clang sanitizer tests (strict)", + "inherits": "test-common", + "configurePreset": "ci-sanitizer-clang" + }, + { + "name": "test-ci-windows-serial", + "displayName": "CI Windows serial tests (strict)", + "inherits": "test-common", + "configurePreset": "ci-windows-serial" + } + ] +} diff --git a/CMakeUserPresets.json.example b/CMakeUserPresets.json.example new file mode 100644 index 00000000..7fd49cf2 --- /dev/null +++ b/CMakeUserPresets.json.example @@ -0,0 +1,67 @@ +{ + "version": 9, + "cmakeMinimumRequired": { + "major": 4, + "minor": 0, + "patch": 0 + }, + "configurePresets": [ + { + "name": "local-common", + "description": "Settings shared by local workstation configurations", + "hidden": true + }, + { + "name": "local-clang-debug", + "displayName": "Local Clang Debug", + "description": "Build KaHIP locally with the Clang supplied by devenv in Debug mode", + "inherits": [ + "local-common", + "unix-clang-debug" + ] + }, + { + "name": "local-clang-release", + "displayName": "Local Clang Release", + "description": "Build KaHIP locally with the Clang supplied by devenv in Release mode", + "inherits": [ + "local-common", + "unix-clang-release" + ] + } + ], + "buildPresets": [ + { + "name": "build-local-common", + "description": "Build settings shared by local workstation configurations", + "hidden": true, + "jobs": 2 + }, + { + "name": "build-local-clang-debug", + "description": "Build the local Clang Debug configuration", + "inherits": "build-local-common", + "configurePreset": "local-clang-debug" + }, + { + "name": "build-local-clang-release", + "description": "Build the local Clang Release configuration", + "inherits": "build-local-common", + "configurePreset": "local-clang-release" + } + ], + "testPresets": [ + { + "name": "test-local-clang-debug", + "displayName": "Local Clang Debug tests (strict)", + "inherits": "test-common", + "configurePreset": "local-clang-debug" + }, + { + "name": "test-local-clang-release", + "displayName": "Local Clang Release tests (strict)", + "inherits": "test-common", + "configurePreset": "local-clang-release" + } + ] +} diff --git a/README.md b/README.md index 7afb28f4..3145c1d5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ KaHIP v3.25 [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![C++](https://img.shields.io/badge/C++-11/14-blue.svg)](https://isocpp.org/) -[![CMake](https://img.shields.io/badge/CMake-3.10+-064F8C.svg)](https://cmake.org/) +[![C++](https://img.shields.io/badge/C++-23-blue.svg)](https://isocpp.org/) +[![CMake](https://img.shields.io/badge/CMake-4.0+-064F8C.svg)](https://cmake.org/) [![Build](https://github.com/KaHIP/KaHIP/actions/workflows/build.yml/badge.svg)](https://github.com/KaHIP/KaHIP/actions/workflows/build.yml) [![Windows CI](https://github.com/KaHIP/KaHIP/actions/workflows/build_windows.yml/badge.svg)](https://github.com/KaHIP/KaHIP/actions/workflows/build_windows.yml) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/9d0d08ba6b2d42699ab74fe5f9697bb9)](https://www.codacy.com/gh/KaHIP/KaHIP/dashboard?utm_source=github.com&utm_medium=referral&utm_content=KaHIP/KaHIP&utm_campaign=Badge_Grade) @@ -155,44 +155,155 @@ You can download KaHIP with the following command line: git clone https://github.com/KaHIP/KaHIP ``` -## Compiling KaHIP: -Before you can start, you need to install the following software packages: +## Compiling KaHIP -- if you want to use parallel algorithms contained within the framework (e.g. ParHIP), you need OpenMPI (https://www.open-mpi.org/). If you don't want to run ParHIP, you can easily get rid of this dependency. +KaHIP requires a C++23 compiler and CMake 4.0 or newer. MPI 3.1 is the portable minimum for ParHIP, while MPI 4 implementations enable additional collective paths when available. + +The supported local development environment uses [devenv](https://devenv.sh/). Its checked-in lockfile supplies CMake 4, Clang, Ninja, pkg-config, Catch2 3, and MPICH without adding those packages to KaHIP's installed link interface. MPICH 4.3.2 is selected through [nixpkgs-multiverse](https://devenv.sh/packages/#installing-a-specific-version), independently of the rolling toolchain input: -Once you installed the packages, just type ```console -./compile_withcmake.sh +devenv shell +cmake --fresh --preset unix-clang-release -DNONATIVEOPTIMIZATIONS=ON +cmake --build --preset build-unix-clang-release --parallel 2 +ctest --preset test-unix-clang-release ``` -In this case, all binaries, libraries and headers are in the folder ./deploy/ -Note that this script detects the amount of available cores on your machine and uses all of them for the compilation process. If you don't want that, set the variable NCORES to the number of cores that you would like to use for compilation. +From outside the shell, `devenv tasks run kahip:test` performs the same +configure, build, and test sequence. Fresh configuration prevents cached MPI +paths from surviving a development-package update. -Alternatively use the standard cmake build process: -```console -mkdir build -cd build -cmake ../ -DCMAKE_BUILD_TYPE=Release -make -cd .. +GCC debug and release presets are named `unix-gcc-debug` and `unix-gcc-release` on systems where GCC is available. Build output is placed below `out/build/` and staged installs below `out/install/`. `CMakeUserPresets.json.example` shows optional local Clang aliases; copy it to the ignored `CMakeUserPresets.json` only when those aliases are useful. Run `devenv update` only when intentionally refreshing the locked development packages. + +The ordinary CMake workflow remains supported for package builds and custom toolchains. Disable tests when Catch2 3 is not provided by the surrounding environment: + +```console +cmake -S . -B out/build/release -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_TESTING=OFF +cmake --build out/build/release --parallel 2 +``` + +Set `-DNOMPI=ON -DPARHIP=OFF` for a serial-only build. Tests follow standard CMake/CTest behavior and can be disabled with `-DBUILD_TESTING=OFF`. + +On Cirrus, use the installed programming-environment modules and Cray compiler wrappers; do not install a second toolchain. The existing release presets remain `BUILD_TESTING=OFF`, so ordinary production builds do not require Catch2. From the KaHIP checkout, the Cray Clang 19 release path is: + +```console +module restore +module load PrgEnv-cray/8.6.0 +module load cmake/4.1.2 +cmake --preset cirrus-cray-release +cmake --build --preset build-cirrus-cray-release +``` + +For the GNU release path, load the supplied Cirrus GNU module before configuring: + +```console +module restore +module load ccs/gnu-2026-06 +module load cmake/4.1.2 +cmake --preset cirrus-gnu-release +cmake --build --preset build-cirrus-gnu-release +``` + +These Cirrus presets use `cc` and `CC`, Cray MPICH, Unix Makefiles, and build entirely below `out/build/`. Neither the portable Unix presets nor the Cirrus presets select the checked-in vcpkg manifest. + +For user-submitted Cirrus test jobs, the test presets enable `BUILD_TESTING` and use `srun` for each MPI test. They require a compatible Catch2 3 installation, but keep its prefix out of portable project presets. The account-specific Slurm scripts supply their own prefix only at configure time, run CTest sequentially, and inherit the normal exclusion of `large` and `performance` labels. They have not been run by local validation or CI. + +```console +cd /work/e609/e609/eriche609/KaHIP +mkdir -p out/slurm +sbatch ci/cirrus/run-cray-tests.slurm +sbatch ci/cirrus/run-gnu-tests.slurm ``` -In this case, the binaries, libraries and headers are in the folder ./build as well as ./build/parallel/parallel_src/ -We also provide the option to link against TCMalloc. If you have it installed, run cmake with the additional option -DUSE_TCMALLOC=On. +Use an appropriately sized allocation for an opt-in large suite, for example: + +```console +ctest --test-dir out/build/cirrus-cray-tests --output-on-failure -L large +``` + +### Cirrus cube scale probe + +The dedicated GNU and Cray scale runners accept the exported +`KAHIP_SCALE_PROBE_SIDE` value and default to the `600^3` regression. Each +supported side has exactly one valid allocation; the runner rejects any other +side, node count, task count, tasks-per-node value, or CPUs-per-task value +before loading modules or configuring the build. + +| Cube | Nodes | MPI ranks | Ranks per node | Expected balance bound | +|---:|---:|---:|---:|---:| +| `600^3` | 8 | 2304 | 288 | 96562 | +| `755^3` | 16 | 4608 | 288 | 96198 | +| `900^3` | 27 | 7776 | 288 | 96562 | +| `1008^3` | 38 | 10944 | 288 | 96392 | + +The jobs use account `e609`, the standard partition and QoS, exclusive nodes, +one CPU per rank, and `OMP_NUM_THREADS=1`. They fresh-configure the existing +`cirrus-gnu-tests` or `cirrus-cray-tests` build tree, pass the existing +`/work/e609/e609/eriche609/opt/catch2` prefix only to that configure command, +and build only `parhip_cube_scale_probe`; they do not install anything. The +launch is unfiltered and uses `srun` with `--hint=nomultithread`, +`--distribution=block:block`, `--kill-on-bad-exit`, and `--unbuffered` so that +failure diagnostics remain in the Slurm record. + +Before configuring, each runner reads the checkout's exact `HEAD` and checks +tracked changes with repository-confined, read-only Git commands. A tracked +difference appends `-dirty`; the resulting token is configured as +`KAHIP_SCALE_PROBE_SOURCE_REVISION`, and an invalid or `unknown` revision is +rejected. Build products, temporary files, and Slurm output remain below +`/work/e609/e609/eriche609/KaHIP`. The `out/slurm` directory must exist before +submission because Slurm opens the output file before starting the script. + +These are user-owned scale runs. They have not been submitted or executed by +local validation or CI. From the Cirrus checkout, create the output directory, +then submit the GNU gates in order. Continue only after the preceding job's +canonical probe record passes all invariants: + +```console +cd /work/e609/e609/eriche609/KaHIP +mkdir -p /work/e609/e609/eriche609/KaHIP/out/slurm +sbatch ci/cirrus/run-gnu-scale-probe.slurm +sbatch --nodes=16 --ntasks=4608 --ntasks-per-node=288 --cpus-per-task=1 --export=ALL,KAHIP_SCALE_PROBE_SIDE=755 ci/cirrus/run-gnu-scale-probe.slurm +sbatch --nodes=27 --ntasks=7776 --ntasks-per-node=288 --cpus-per-task=1 --export=ALL,KAHIP_SCALE_PROBE_SIDE=900 ci/cirrus/run-gnu-scale-probe.slurm +sbatch --nodes=38 --ntasks=10944 --ntasks-per-node=288 --cpus-per-task=1 --export=ALL,KAHIP_SCALE_PROBE_SIDE=1008 ci/cirrus/run-gnu-scale-probe.slurm +``` + +Confirm the baseline with Cray after GNU passes `600^3`: + +```console +sbatch ci/cirrus/run-cray-scale-probe.slurm +``` + +Then confirm the largest GNU-passing gate with the matching exact command +below. If `600^3` is the largest pass, the baseline Cray job above is already +that confirmation. + +```console +# Largest GNU pass: 755^3 +sbatch --nodes=16 --ntasks=4608 --ntasks-per-node=288 --cpus-per-task=1 --export=ALL,KAHIP_SCALE_PROBE_SIDE=755 ci/cirrus/run-cray-scale-probe.slurm +# Largest GNU pass: 900^3 +sbatch --nodes=27 --ntasks=7776 --ntasks-per-node=288 --cpus-per-task=1 --export=ALL,KAHIP_SCALE_PROBE_SIDE=900 ci/cirrus/run-cray-scale-probe.slurm +# Largest GNU pass: 1008^3 +sbatch --nodes=38 --ntasks=10944 --ntasks-per-node=288 --cpus-per-task=1 --export=ALL,KAHIP_SCALE_PROBE_SIDE=1008 ci/cirrus/run-cray-scale-probe.slurm +``` + +See the [Cirrus application-development guide](https://docs.cirrus.ac.uk/user-guide/development/) and [Cirrus batch-job guide](https://docs.cirrus.ac.uk/user-guide/batch/) for the supported environments and scheduler guidance. + +We also provide the option to link against TCMalloc. If you have it installed, configure with `-DUSE_TCMALLOC=ON`. By default node ordering programs are also compiled. If you have Metis installed, the build script also compiles a faster node ordering program that uses reductions before calling Metis ND. Note that Metis requires GKlib (https://github.com/KarypisLab/GKlib). -If you use the option -DUSE_ILP=On and you have Gurobi installed, the build script compiles the ILP programs to improve a given partition *ilp_improve* and an exact solver *ilp_exact*. Alternatively, you can also pass these options to ./compile_withmake.sh for example: +If you use the option `-DUSE_ILP=ON` and have Gurobi installed, CMake builds the ILP program *ilp_improve* and the exact solver *ilp_exact*: ```console -./compile_withcmake -DUSE_ILP=On +cmake --preset unix-gcc-release -DUSE_ILP=ON ``` -We also provide an option to support 64-bit edges. In order to use this, compile KaHIP with the option -D64BITMODE=On. When enabled, the C interface uses `kahip_idx` (typedef for `int64_t`) instead of `int32_t` for all edge-related arrays and values (xadj, adjncy, adjcwgt, edgecut, infinity_edge_weight). Node-related parameters (n, vwgt, nparts, part) remain `int`. No additional flags are needed; `-D64BITMODE=On` enables both the internal 64-bit edge types and the public `kahip_idx` typedef. +We also provide an option to support 64-bit edges. Configure with `-D64BITMODE=ON`. When enabled, the C interface uses `kahip_idx` (an `int64_t` typedef) instead of `int32_t` for all edge-related arrays and values (`xadj`, `adjncy`, `adjcwgt`, `edgecut`, and `infinity_edge_weight`). Node-related parameters (`n`, `vwgt`, `nparts`, and `part`) remain `int`. -Lastly, we provide an option for determinism in ParHIP, e.g. two runs with the same seed will give you the same result. Note however that this option can reduce the quality of partitions, as initial partitioning algorithms do not use sophisticated memetic algorithms, but only multilevel algorithms to compute initial partitionings. ONLY use this option if you use ParHIP as a tool. Do not use this option if you want to make quality comparisons against ParHIP. To make use of this option, run +Lastly, we provide an option for determinism in ParHIP, e.g. two runs with the same seed will give you the same result. Note however that this option can reduce the quality of partitions, as initial partitioning algorithms do not use sophisticated memetic algorithms, but only multilevel algorithms to compute initial partitionings. ONLY use this option if you use ParHIP as a tool. Do not use this option if you want to make quality comparisons against ParHIP. To make use of this option, configure with: ```console -./compile_withcmake -DDETERMINISTIC_PARHIP=On +cmake --preset unix-gcc-release -DDETERMINISTIC_PARHIP=ON ``` Running Programs diff --git a/app/kaffpaE.cpp b/app/kaffpaE.cpp index 0684fd53..e5a61001 100644 --- a/app/kaffpaE.cpp +++ b/app/kaffpaE.cpp @@ -1,142 +1,148 @@ /****************************************************************************** - * kaffpaE.cpp + * kaffpaE.cpp * * * Source of KaHIP -- Karlsruhe High Quality Partitioning. * Christian Schulz *****************************************************************************/ #include +#include + +#include #include -#include -#include +#include #include -#ifndef _WIN32 -#include -#endif #include -#include -#include +#include +#include #include "algorithms/cycle_search.h" #include "balance_configuration.h" #include "data_structure/graph_access.h" #include "graph_io.h" -#include "macros_assertions.h" +#include "mpi_application_runtime.h" #include "parallel_mh/parallel_mh_async.h" #include "parse_parameters.h" #include "partition/graph_partitioner.h" #include "partition/partition_config.h" #include "quality_metrics.h" -#include "random_functions.h" #include "timer.h" -#include - -int main(int argn, char **argv) { - - MPI_Init(&argn, &argv); /* starts MPI */ - PartitionConfig partition_config; - std::string graph_filename; - bool is_graph_weighted = false; - bool suppress_output = false; - bool recursive = false; - - int ret_code = parse_parameters(argn, argv, - partition_config, graph_filename, - is_graph_weighted, suppress_output, - recursive); +int main(int argument_count, char** argument_values) { + kahip::mpi::application_runtime runtime{argument_count, argument_values, + "kaffpaE executable"}; + return runtime.execute([&](MPI_Comm communicator) -> int { + auto partition_config = PartitionConfig{}; + auto graph_filename = std::string{}; + auto is_graph_weighted = false; + auto suppress_output = false; + auto recursive = false; + auto early_exit = false; + if (parse_parameters(argument_count, argument_values, partition_config, + graph_filename, is_graph_weighted, + suppress_output, recursive, &early_exit) != 0) { + return early_exit ? EXIT_SUCCESS : EXIT_FAILURE; + } - if(ret_code) { - return 0; + auto rank = 0; + kahip::mpi::check_or_abort(MPI_Comm_rank(communicator, &rank), + communicator, "kaffpaE executable", + "MPI_Comm_rank(kaffpaE operation)"); + if (partition_config.k == 0 || + partition_config.k > + static_cast(std::numeric_limits::max())) { + if (rank == ROOT) { + std::cerr << "Number of blocks must be a positive int.\n"; + } + return EXIT_FAILURE; } partition_config.LogDump(stdout); - partition_config.graph_filename = graph_filename.substr( graph_filename.find_last_of( '/' ) +1 ); - - graph_access G; - - timer t; - graph_io::readGraphWeighted(G, graph_filename); - - std::cout << "io time: " << t.elapsed() << std::endl; - - if(partition_config.connected_blocks) { - std::vector visited(G.number_of_nodes(), false); - std::queue bfs_queue; - visited[0] = true; - bfs_queue.push(0); - NodeID visited_count = 1; - while(!bfs_queue.empty()) { - NodeID v = bfs_queue.front(); bfs_queue.pop(); - forall_out_edges(G, e, v) { - NodeID u = G.getEdgeTarget(e); - if(!visited[u]) { visited[u] = true; visited_count++; bfs_queue.push(u); } - } endfor - } - if(visited_count < G.number_of_nodes()) { - std::cout << "WARNING: input graph is disconnected, connected blocks cannot be guaranteed." << std::endl; - } + partition_config.graph_filename = + graph_filename.substr(graph_filename.find_last_of('/') + 1); + auto graph = graph_access{}; + auto clock = timer{}; + graph_io::readGraphWeighted(graph, graph_filename); + std::cout << "io time: " << clock.elapsed() << '\n'; + + if (partition_config.connected_blocks && graph.number_of_nodes() > 0) { + auto visited = std::vector(graph.number_of_nodes(), false); + auto pending = std::queue{}; + visited.front() = true; + pending.push(0); + auto visited_count = NodeID{1}; + while (!pending.empty()) { + auto const vertex = pending.front(); + pending.pop(); + forall_out_edges(graph, edge, vertex) { + auto const target = graph.getEdgeTarget(edge); + if (!visited[target]) { + visited[target] = true; + ++visited_count; + pending.push(target); + } + } + endfor + } + if (visited_count < graph.number_of_nodes()) { + std::cout << "WARNING: input graph is disconnected, connected " + "blocks cannot be guaranteed.\n"; + } } omp_set_num_threads(1); - G.set_partition_count(partition_config.k); - partition_config.kaffpaE = true; // necessary for balance configuration - if( partition_config.imbalance < 1 ) { - partition_config.kabapE = true; + graph.set_partition_count(partition_config.k); + partition_config.kaffpaE = true; + if (partition_config.imbalance < 1) { + partition_config.kabapE = true; } - - balance_configuration bc; - bc.configurate_balance( partition_config, G); - - std::vector input_partition; - if(partition_config.input_partition != "") { - std::cout << "reading input partition" << std::endl; - graph_io::readPartition(G, partition_config.input_partition); - partition_config.graph_allready_partitioned = true; - - input_partition.resize(G.number_of_nodes()); - - forall_nodes(G, node) { - input_partition[node] = G.getPartitionIndex(node); - } endfor + auto balance = balance_configuration{}; + balance.configurate_balance(partition_config, graph); + + auto input_partition = std::vector{}; + if (!partition_config.input_partition.empty()) { + std::cout << "reading input partition\n"; + graph_io::readPartition(graph, partition_config.input_partition); + partition_config.graph_allready_partitioned = true; + input_partition.resize(graph.number_of_nodes()); + forall_nodes(graph, node) { + input_partition[node] = graph.getPartitionIndex(node); + } + endfor } - t.restart(); - - parallel_mh_async mh; - mh.perform_partitioning(partition_config, G); - - - int rank, size; - MPI_Comm communicator = MPI_COMM_WORLD; - MPI_Comm_rank( communicator, &rank); - MPI_Comm_size( communicator, &size); - - if( rank == ROOT ) { - std::cout << "time spent for partitioning " << t.elapsed() << std::endl; - std::cout << "time spent in neg. cycle detection " << cycle_search::total_time << std::endl; - std::cout << "time spent in neg. cycle detection (rel) " << (cycle_search::total_time/t.elapsed()*100) << std::endl; - - // output some information about the partition that we have computed - quality_metrics qm; - EdgeWeight cut = qm.edge_cut(G); - std::cout << "cut \t\t" << cut << std::endl; - std::cout << "finalobjective " << cut << std::endl; - std::cout << "bnd \t\t" << qm.boundary_nodes(G) << std::endl; - std::cout << "balance \t" << qm.balance(G) << std::endl; - std::cout << "max_comm_vol \t" << qm.max_communication_volume(G) << std::endl; - - // write the partition to the disc - std::stringstream filename; - if(!partition_config.filename_output.compare("")) { - // no output filename given - filename << "tmppartition" << partition_config.k; - } else { - filename << partition_config.filename_output; - } - - graph_io::writePartition(G, filename.str()); + clock.restart(); + auto metaheuristic = + parallel_mh_async{communicator}; + metaheuristic.perform_partitioning(partition_config, graph); + auto const elapsed = clock.elapsed(); + + if (rank == ROOT) { + std::cout << "time spent for partitioning " << elapsed << '\n'; + std::cout << "time spent in neg. cycle detection " + << cycle_search::total_time << '\n'; + auto const relative_cycle_time = + elapsed > 0.0 ? cycle_search::total_time / elapsed * 100.0 : 0.0; + std::cout << "time spent in neg. cycle detection (rel) " + << relative_cycle_time << '\n'; + + auto quality = quality_metrics{}; + auto const cut = quality.edge_cut(graph); + std::cout << "cut \t\t" << cut << '\n'; + std::cout << "finalobjective " << cut << '\n'; + std::cout << "bnd \t\t" << quality.boundary_nodes(graph) << '\n'; + std::cout << "balance \t" << quality.balance(graph) << '\n'; + std::cout << "max_comm_vol \t" + << quality.max_communication_volume(graph) << '\n'; + + auto filename = std::stringstream{}; + if (partition_config.filename_output.empty()) { + filename << "tmppartition" << partition_config.k; + } else { + filename << partition_config.filename_output; + } + graph_io::writePartition(graph, filename.str()); } - - MPI_Finalize(); + return EXIT_SUCCESS; + }); } diff --git a/app/mpi_application_runtime.cpp b/app/mpi_application_runtime.cpp new file mode 100644 index 00000000..55ec1047 --- /dev/null +++ b/app/mpi_application_runtime.cpp @@ -0,0 +1,127 @@ +#include "mpi_application_runtime.h" + +#include +#include +#include + +#include "tools/fatal_diagnostics.h" + +namespace kahip::mpi { +namespace { +[[noreturn]] void abort_without_mpi(int error_code, + std::string_view boundary, + std::string_view operation, + int rank) noexcept { + if (rank >= 0) { + kahip::diagnostics::critical( + "MPI lifecycle failure: ", boundary, ": ", operation, + " returned raw error ", error_code, " on rank ", rank); + } else { + kahip::diagnostics::critical( + "MPI lifecycle failure: ", boundary, ": ", operation, + " returned raw error ", error_code, " (rank unavailable)"); + } + std::abort(); +} + +[[noreturn]] void abort_backend(MPI_Comm communicator, + int error_code, + std::string_view boundary, + std::string_view operation, + int rank) noexcept { + if (rank >= 0) { + kahip::diagnostics::critical( + "MPI backend failure: ", boundary, ": ", operation, + " returned raw error ", error_code, " on rank ", rank); + } else { + kahip::diagnostics::critical( + "MPI backend failure: ", boundary, ": ", operation, + " returned raw error ", error_code, " (rank unavailable)"); + } + static_cast(MPI_Abort(communicator, EXIT_FAILURE)); + std::abort(); +} +} // namespace + +void check_or_abort(int result, + MPI_Comm communicator, + std::string_view boundary, + std::string_view operation) noexcept { + if (result == MPI_SUCCESS) { + return; + } + auto rank = -1; + static_cast(PMPI_Comm_rank(communicator, &rank)); + abort_backend(communicator, result, boundary, operation, rank); +} + +application_runtime::application_runtime(int& argument_count, + char**& argument_values, + std::string_view boundary) + : boundary_(boundary) { + auto const result = MPI_Init(&argument_count, &argument_values); + if (result != MPI_SUCCESS) { + abort_without_mpi(result, boundary_, "MPI_Init", -1); + } + auto const rank_result = MPI_Comm_rank(MPI_COMM_WORLD, &rank_); + if (rank_result != MPI_SUCCESS) { + abort_backend(MPI_COMM_WORLD, rank_result, boundary_, + "MPI_Comm_rank(application runtime)", -1); + } +} + +application_runtime::~application_runtime() noexcept { + auto const result = MPI_Finalize(); + if (result != MPI_SUCCESS) { + abort_without_mpi(result, boundary_, "MPI_Finalize", rank_); + } +} + +auto application_runtime::duplicate_operation_communicator() const noexcept + -> MPI_Comm { + auto communicator = MPI_COMM_NULL; + auto const duplicate_result = MPI_Comm_dup(MPI_COMM_WORLD, &communicator); + if (duplicate_result != MPI_SUCCESS) { + abort_backend(MPI_COMM_WORLD, duplicate_result, boundary_, + "MPI_Comm_dup(application operation communicator)", rank_); + } + auto const handler_result = + MPI_Comm_set_errhandler(communicator, MPI_ERRORS_RETURN); + if (handler_result != MPI_SUCCESS) { + abort_backend( + communicator, handler_result, boundary_, + "MPI_Comm_set_errhandler(application operation communicator)", rank_); + } + return communicator; +} + +void application_runtime::free_operation_communicator( + MPI_Comm communicator) const noexcept { + auto owned = communicator; + auto const result = MPI_Comm_free(&owned); + if (result != MPI_SUCCESS) { + abort_backend(MPI_COMM_WORLD, result, boundary_, + "MPI_Comm_free(application operation communicator)", rank_); + } +} + +[[noreturn]] void application_runtime::abort_on_exception( + MPI_Comm communicator, + std::exception_ptr exception) const noexcept { + auto diagnostic = std::string{"unknown operation failure"}; + if (exception != nullptr) { + try { + std::rethrow_exception(exception); + } catch (std::exception const& error) { + diagnostic = error.what(); + } catch (...) { + diagnostic = "non-standard operation exception"; + } + } + kahip::diagnostics::critical(boundary_, ": ", diagnostic, " (rank ", rank_, + ")"); + static_cast(MPI_Abort(communicator, EXIT_FAILURE)); + std::abort(); +} + +} // namespace kahip::mpi diff --git a/app/mpi_application_runtime.h b/app/mpi_application_runtime.h new file mode 100644 index 00000000..8a261d06 --- /dev/null +++ b/app/mpi_application_runtime.h @@ -0,0 +1,62 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace kahip::mpi { + +void check_or_abort(int result, + MPI_Comm communicator, + std::string_view boundary, + std::string_view operation) noexcept; + +// Root KaHIP executables own their MPI lifecycle. The duplicated operation +// communicator and every object created from it are destroyed before Finalize. +class application_runtime final { + public: + application_runtime(int& argument_count, + char**& argument_values, + std::string_view boundary); + ~application_runtime() noexcept; + + application_runtime(application_runtime const&) = delete; + auto operator=(application_runtime const&) -> application_runtime& = delete; + application_runtime(application_runtime&&) = delete; + auto operator=(application_runtime&&) -> application_runtime& = delete; + + template + requires std::invocable && + std::same_as, int> + [[nodiscard]] auto execute(Operation&& operation) noexcept -> int { + auto operation_communicator = duplicate_operation_communicator(); + try { + auto const result = + std::invoke(std::forward(operation), + operation_communicator); + free_operation_communicator(operation_communicator); + return result; + } catch (...) { + abort_on_exception(operation_communicator, std::current_exception()); + } + } + + private: + [[nodiscard]] auto duplicate_operation_communicator() const noexcept + -> MPI_Comm; + void free_operation_communicator(MPI_Comm communicator) const noexcept; + [[noreturn]] void abort_on_exception( + MPI_Comm communicator, + std::exception_ptr exception) const noexcept; + + std::string boundary_; + int rank_ = -1; +}; + +} // namespace kahip::mpi diff --git a/app/parse_parameters.h b/app/parse_parameters.h index 33c7960e..6f0c7828 100644 --- a/app/parse_parameters.h +++ b/app/parse_parameters.h @@ -11,6 +11,10 @@ #ifdef USE_OPENMP #include #endif +#ifndef _WIN32 +#include +#endif +#include #include #include #ifdef _WIN32 @@ -26,7 +30,12 @@ int parse_parameters(int argn, char **argv, std::string & graph_filename, bool & is_graph_weighted, bool & suppress_program_output, - bool & recursive) { + bool & recursive, + bool *requested_early_exit = nullptr) { + + if (requested_early_exit != nullptr) { + *requested_early_exit = false; + } const char *progname = argv[0]; @@ -359,6 +368,9 @@ int parse_parameters(int argn, char **argv, int nerrors = arg_parse(argn, argv, argtable); if (version->count > 0) { + if (requested_early_exit != nullptr) { + *requested_early_exit = true; + } std::cout << KAHIPVERSION << std::endl; arg_freetable(argtable_fordeletion, sizeof(argtable_fordeletion) / sizeof(argtable_fordeletion[0])); return 1; @@ -366,6 +378,9 @@ int parse_parameters(int argn, char **argv, // Catch case that help was requested. if (help->count > 0) { + if (requested_early_exit != nullptr) { + *requested_early_exit = true; + } printf("Usage: %s", progname); arg_print_syntax(stdout, argtable, "\n"); arg_print_glossary(stdout, argtable," %-40s %s\n"); diff --git a/ci/bootstrap-vcpkg.ps1 b/ci/bootstrap-vcpkg.ps1 new file mode 100644 index 00000000..a05298ab --- /dev/null +++ b/ci/bootstrap-vcpkg.ps1 @@ -0,0 +1,29 @@ +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +if (-not $env:VCPKG_ROOT) { + throw "VCPKG_ROOT must name the CI vcpkg checkout" +} + +$manifest = Get-Content (Join-Path $env:GITHUB_WORKSPACE "vcpkg.json") -Raw | + ConvertFrom-Json +$manifestBaseline = $manifest.'builtin-baseline' + +if ($env:VCPKG_BASELINE -and $env:VCPKG_BASELINE -ne $manifestBaseline) { + throw "VCPKG_BASELINE ($env:VCPKG_BASELINE) disagrees with vcpkg.json ($manifestBaseline)" +} + +git init --quiet $env:VCPKG_ROOT +git -C $env:VCPKG_ROOT remote add origin https://github.com/microsoft/vcpkg.git +git -C $env:VCPKG_ROOT fetch --quiet --depth 1 origin $manifestBaseline +git -C $env:VCPKG_ROOT checkout --quiet --detach FETCH_HEAD + +$actualBaseline = (git -C $env:VCPKG_ROOT rev-parse HEAD).Trim() +if ($actualBaseline -ne $manifestBaseline) { + throw "vcpkg checkout is $actualBaseline, expected $manifestBaseline" +} + +& (Join-Path $env:VCPKG_ROOT "bootstrap-vcpkg.bat") -disableMetrics +if ($LASTEXITCODE -ne 0) { + throw "vcpkg bootstrap failed with exit code $LASTEXITCODE" +} diff --git a/ci/bootstrap-vcpkg.sh b/ci/bootstrap-vcpkg.sh new file mode 100755 index 00000000..d62398ee --- /dev/null +++ b/ci/bootstrap-vcpkg.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +set -euo pipefail + +: "${VCPKG_ROOT:?VCPKG_ROOT must name the CI vcpkg checkout}" + +manifest_baseline="$( + python3 - "${GITHUB_WORKSPACE:-.}/vcpkg.json" <<'PY' +import json +import pathlib +import sys + +manifest = pathlib.Path(sys.argv[1]) +print(json.loads(manifest.read_text(encoding="utf-8"))["builtin-baseline"]) +PY +)" + +if [[ -n "${VCPKG_BASELINE:-}" && "${VCPKG_BASELINE}" != "${manifest_baseline}" ]]; then + printf 'VCPKG_BASELINE (%s) disagrees with vcpkg.json (%s)\n' \ + "${VCPKG_BASELINE}" "${manifest_baseline}" >&2 + exit 1 +fi + +git init --quiet "${VCPKG_ROOT}" +git -C "${VCPKG_ROOT}" remote add origin https://github.com/microsoft/vcpkg.git +git -C "${VCPKG_ROOT}" fetch --quiet --depth 1 origin "${manifest_baseline}" +git -C "${VCPKG_ROOT}" checkout --quiet --detach FETCH_HEAD + +actual_baseline="$(git -C "${VCPKG_ROOT}" rev-parse HEAD)" +if [[ "${actual_baseline}" != "${manifest_baseline}" ]]; then + printf 'vcpkg checkout is %s, expected %s\n' \ + "${actual_baseline}" "${manifest_baseline}" >&2 + exit 1 +fi + +"${VCPKG_ROOT}/bootstrap-vcpkg.sh" -disableMetrics diff --git a/ci/cirrus/VerifyScaleRunners.cmake b/ci/cirrus/VerifyScaleRunners.cmake new file mode 100644 index 00000000..94b66f50 --- /dev/null +++ b/ci/cirrus/VerifyScaleRunners.cmake @@ -0,0 +1,853 @@ +cmake_minimum_required(VERSION 4.0) + +foreach(required_variable IN ITEMS SOURCE_DIR BASH_EXECUTABLE TEST_ROOT) + if( + NOT DEFINED ${required_variable} + OR "${${required_variable}}" STREQUAL "" + ) + message(FATAL_ERROR "${required_variable} is required") + endif() +endforeach() + +function(require_contains value expected context) + string(FIND "${value}" "${expected}" expected_index) + if(expected_index EQUAL -1) + message( + FATAL_ERROR + "${context}: missing '${expected}'\n--- value ---\n${value}" + ) + endif() +endfunction() + +function(require_not_contains value forbidden context) + string(FIND "${value}" "${forbidden}" forbidden_index) + if(NOT forbidden_index EQUAL -1) + message( + FATAL_ERROR + "${context}: found forbidden '${forbidden}'\n--- value ---\n${value}" + ) + endif() +endfunction() + +function(require_equal actual expected context) + if(NOT "${actual}" STREQUAL "${expected}") + message( + FATAL_ERROR + "${context}: values differ\n--- expected ---\n${expected}--- actual ---\n${actual}" + ) + endif() +endfunction() + +function(require_success result output context) + if(NOT "${result}" STREQUAL "0") + message( + FATAL_ERROR + "${context}: runner failed with ${result}\n--- output ---\n${output}" + ) + endif() +endfunction() + +function(require_failure result output context) + if("${result}" STREQUAL "0") + message( + FATAL_ERROR + "${context}: runner unexpectedly succeeded\n--- output ---\n${output}" + ) + endif() +endfunction() + +function(write_executable path content) + file(WRITE "${path}" "${content}") + file( + CHMOD "${path}" + PERMISSIONS + OWNER_READ OWNER_WRITE OWNER_EXECUTE + GROUP_READ GROUP_EXECUTE + WORLD_READ WORLD_EXECUTE + ) +endfunction() + +set(module_stub [=[#!/usr/bin/env bash +set -euo pipefail +printf 'module|pwd=<%s>|tmpdir=<%s>|omp=<%s>' \ + "${PWD}" "${TMPDIR-}" "${OMP_NUM_THREADS-}" >> "${KAHIP_STUB_LOG}" +for argument in "$@"; do + printf '|arg=<%s>' "${argument}" >> "${KAHIP_STUB_LOG}" +done +printf '\n' >> "${KAHIP_STUB_LOG}" +]=]) + +set(git_stub [=[#!/usr/bin/env bash +set -euo pipefail +printf 'git|pwd=<%s>|tmpdir=<%s>|omp=<%s>' \ + "${PWD}" "${TMPDIR-}" "${OMP_NUM_THREADS-}" >> "${KAHIP_STUB_LOG}" +for argument in "$@"; do + printf '|arg=<%s>' "${argument}" >> "${KAHIP_STUB_LOG}" +done +printf '\n' >> "${KAHIP_STUB_LOG}" + +if [[ ${1-} != -C || ${2-} != "${KAHIP_STUB_REPO_ROOT}" ]]; then + exit 90 +fi +shift 2 +case "${1-}" in + rev-parse) + if [[ $# == 2 && ${2-} == --show-toplevel ]]; then + printf '%s\n' "${KAHIP_STUB_REPO_ROOT}" + elif [[ $# == 3 && ${2-} == --verify && ${3-} == 'HEAD^{commit}' ]]; then + printf '%s\n' "${KAHIP_STUB_HEAD}" + else + exit 91 + fi + ;; + diff) + if [[ $# != 5 || ${2-} != --quiet || ${3-} != --no-ext-diff || ${4-} != HEAD || ${5-} != -- ]]; then + exit 92 + fi + exit "${KAHIP_STUB_DIFF_STATUS}" + ;; + *) + exit 93 + ;; +esac +]=]) + +set(cmake_stub [=[#!/usr/bin/env bash +set -euo pipefail +printf 'cmake|pwd=<%s>|tmpdir=<%s>|omp=<%s>' \ + "${PWD}" "${TMPDIR-}" "${OMP_NUM_THREADS-}" >> "${KAHIP_STUB_LOG}" +for argument in "$@"; do + printf '|arg=<%s>' "${argument}" >> "${KAHIP_STUB_LOG}" +done +printf '\n' >> "${KAHIP_STUB_LOG}" + +case "${1-}" in + --fresh) + [[ $# == 5 ]] || exit 94 + [[ ${2-} == --preset && ${3-} == "${KAHIP_STUB_CONFIGURE_PRESET}" ]] || exit 94 + [[ ${4-} == "-DCMAKE_PREFIX_PATH:PATH=${KAHIP_STUB_CATCH2_PREFIX}" ]] || exit 94 + [[ ${5-} == "-DKAHIP_SCALE_PROBE_SOURCE_REVISION=${KAHIP_STUB_SOURCE_REVISION}" ]] || exit 94 + ;; + --build) + [[ $# == 5 ]] || exit 94 + [[ ${2-} == --preset && ${3-} == "${KAHIP_STUB_BUILD_PRESET}" ]] || exit 94 + [[ ${4-} == --target && ${5-} == parhip_cube_scale_probe ]] || exit 94 + probe="${KAHIP_STUB_REPO_ROOT}/out/build/${KAHIP_STUB_CONFIGURE_PRESET}/parallel/parallel_src/tests/parhip_cube_scale_probe" + mkdir -p "$(dirname "${probe}")" + printf '#!/usr/bin/env bash\nexit 95\n' > "${probe}" + chmod +x "${probe}" + ;; + *) + exit 94 + ;; +esac +]=]) + +set(srun_stub [=[#!/usr/bin/env bash +set -euo pipefail +printf 'srun|pwd=<%s>|tmpdir=<%s>|omp=<%s>' \ + "${PWD}" "${TMPDIR-}" "${OMP_NUM_THREADS-}" >> "${KAHIP_STUB_LOG}" +for argument in "$@"; do + printf '|arg=<%s>' "${argument}" >> "${KAHIP_STUB_LOG}" +done +printf '\n' >> "${KAHIP_STUB_LOG}" +probe="${KAHIP_STUB_REPO_ROOT}/out/build/${KAHIP_STUB_CONFIGURE_PRESET}/parallel/parallel_src/tests/parhip_cube_scale_probe" +[[ $# == 13 ]] || exit 95 +[[ ${1-} == "--nodes=${KAHIP_STUB_NODES}" ]] || exit 95 +[[ ${2-} == "--ntasks=${KAHIP_STUB_TASKS}" ]] || exit 95 +[[ ${3-} == --ntasks-per-node=288 ]] || exit 95 +[[ ${4-} == --cpus-per-task=1 ]] || exit 95 +[[ ${5-} == --hint=nomultithread ]] || exit 95 +[[ ${6-} == --distribution=block:block ]] || exit 95 +[[ ${7-} == --kill-on-bad-exit ]] || exit 95 +[[ ${8-} == --unbuffered ]] || exit 95 +[[ ${9-} == "${probe}" ]] || exit 95 +[[ ${10-} == --side && ${11-} == "${KAHIP_STUB_SIDE}" ]] || exit 95 +[[ ${12-} == --expected-ranks && ${13-} == "${KAHIP_STUB_TASKS}" ]] || exit 95 +printf 'scale-probe-stdout-marker\n' +printf 'scale-probe-stderr-marker\n' >&2 +exit "${KAHIP_STUB_SRUN_STATUS}" +]=]) + +set(sbatch_stub [=[#!/usr/bin/env bash +set -euo pipefail +if [[ $# != 1 || ! -f $1 ]]; then + exit 96 +fi +runner=$1 + +job_name= +wall_time= +exclusive=0 +nodes= +tasks= +tasks_per_node= +cpus_per_task= +account= +partition= +qos= +chdir_path= +output_path= +while IFS= read -r line; do + compact_line=${line//[[:space:]]/} + if [[ -z ${compact_line} || ${line} == '#!'* ]]; then + continue + fi + if [[ ${line} != '#'* ]]; then + break + fi + case "${line}" in + '#SBATCH --job-name='*) job_name=${line#*=} ;; + '#SBATCH --time='*) wall_time=${line#*=} ;; + '#SBATCH --exclusive') exclusive=1 ;; + '#SBATCH --nodes='*) nodes=${line#*=} ;; + '#SBATCH --ntasks='*) tasks=${line#*=} ;; + '#SBATCH --ntasks-per-node='*) tasks_per_node=${line#*=} ;; + '#SBATCH --cpus-per-task='*) cpus_per_task=${line#*=} ;; + '#SBATCH --account='*) account=${line#*=} ;; + '#SBATCH --partition='*) partition=${line#*=} ;; + '#SBATCH --qos='*) qos=${line#*=} ;; + '#SBATCH --chdir='*) chdir_path=${line#*=} ;; + '#SBATCH --output='*) output_path=${line#*=} ;; + esac +done < "${runner}" + +[[ ${job_name} == "kahip-${KAHIP_STUB_COMPILER}-scale" ]] || exit 97 +[[ ${wall_time} == 02:00:00 ]] || exit 98 +[[ ${exclusive} == 1 ]] || exit 99 +[[ ${nodes} == 8 && ${tasks} == 2304 ]] || exit 100 +[[ ${tasks_per_node} == 288 && ${cpus_per_task} == 1 ]] || exit 101 +[[ ${account} == e609 ]] || exit 102 +[[ ${partition} == standard && ${qos} == standard ]] || exit 103 +readonly fixed_repository=/work/e609/e609/eriche609/KaHIP +[[ ${chdir_path} == "${fixed_repository}" ]] || exit 104 +[[ ${output_path} == "${fixed_repository}/out/slurm/%x-%j.out" ]] || exit 105 + +mapped_output=${output_path/#${fixed_repository}/${KAHIP_STUB_REPO_ROOT}} +mapped_output=${mapped_output//%x/${job_name}} +mapped_output=${mapped_output//%j/42001} +[[ -d $(dirname "${mapped_output}") ]] || exit 106 +: > "${mapped_output}" + +printf \ + 'sbatch|job=<%s>|time=<%s>|exclusive=<%s>|nodes=<%s>|tasks=<%s>|tasks-per-node=<%s>|cpus-per-task=<%s>|account=<%s>|partition=<%s>|qos=<%s>|chdir=<%s>|output=<%s>\n' \ + "${job_name}" "${wall_time}" "${exclusive}" "${nodes}" "${tasks}" \ + "${tasks_per_node}" "${cpus_per_task}" "${account}" "${partition}" \ + "${qos}" "${chdir_path}" "${output_path}" >> "${KAHIP_STUB_LOG}" + +export SLURM_JOB_ID=42001 +export SLURM_JOB_NUM_NODES=${nodes} +export SLURM_NTASKS=${tasks} +export SLURM_NTASKS_PER_NODE=${tasks_per_node} +export SLURM_CPUS_PER_TASK=${cpus_per_task} +cd -- "${KAHIP_STUB_REPO_ROOT}" +exec "${KAHIP_STUB_BASH}" "${runner}" +]=]) + +function(make_expected_runner_log output_variable) + set( + one_value_arguments + COMPILER + MODULE + CONFIGURE_PRESET + BUILD_PRESET + REPOSITORY + CATCH2_PREFIX + HEAD + SOURCE_REVISION + SIDE + NODES + TASKS + ) + cmake_parse_arguments(ARG "" "${one_value_arguments}" "" ${ARGN}) + foreach(required_argument IN LISTS one_value_arguments) + if(NOT DEFINED ARG_${required_argument}) + message( + FATAL_ERROR + "make_expected_runner_log requires ${required_argument}" + ) + endif() + endforeach() + + set( + log + "module|pwd=<${ARG_REPOSITORY}>|tmpdir=<${ARG_REPOSITORY}/out/tmp/cirrus-scale-${ARG_COMPILER}-42001>|omp=<1>|arg=\n" + ) + string( + APPEND log + "module|pwd=<${ARG_REPOSITORY}>|tmpdir=<${ARG_REPOSITORY}/out/tmp/cirrus-scale-${ARG_COMPILER}-42001>|omp=<1>|arg=|arg=<${ARG_MODULE}>\n" + "module|pwd=<${ARG_REPOSITORY}>|tmpdir=<${ARG_REPOSITORY}/out/tmp/cirrus-scale-${ARG_COMPILER}-42001>|omp=<1>|arg=|arg=\n" + "git|pwd=<${ARG_REPOSITORY}>|tmpdir=<${ARG_REPOSITORY}/out/tmp/cirrus-scale-${ARG_COMPILER}-42001>|omp=<1>|arg=<-C>|arg=<${ARG_REPOSITORY}>|arg=|arg=<--show-toplevel>\n" + "git|pwd=<${ARG_REPOSITORY}>|tmpdir=<${ARG_REPOSITORY}/out/tmp/cirrus-scale-${ARG_COMPILER}-42001>|omp=<1>|arg=<-C>|arg=<${ARG_REPOSITORY}>|arg=|arg=<--verify>|arg=\n" + "git|pwd=<${ARG_REPOSITORY}>|tmpdir=<${ARG_REPOSITORY}/out/tmp/cirrus-scale-${ARG_COMPILER}-42001>|omp=<1>|arg=<-C>|arg=<${ARG_REPOSITORY}>|arg=|arg=<--quiet>|arg=<--no-ext-diff>|arg=|arg=<-->\n" + "cmake|pwd=<${ARG_REPOSITORY}>|tmpdir=<${ARG_REPOSITORY}/out/tmp/cirrus-scale-${ARG_COMPILER}-42001>|omp=<1>|arg=<--fresh>|arg=<--preset>|arg=<${ARG_CONFIGURE_PRESET}>|arg=<-DCMAKE_PREFIX_PATH:PATH=${ARG_CATCH2_PREFIX}>|arg=<-DKAHIP_SCALE_PROBE_SOURCE_REVISION=${ARG_SOURCE_REVISION}>\n" + "cmake|pwd=<${ARG_REPOSITORY}>|tmpdir=<${ARG_REPOSITORY}/out/tmp/cirrus-scale-${ARG_COMPILER}-42001>|omp=<1>|arg=<--build>|arg=<--preset>|arg=<${ARG_BUILD_PRESET}>|arg=<--target>|arg=\n" + "srun|pwd=<${ARG_REPOSITORY}>|tmpdir=<${ARG_REPOSITORY}/out/tmp/cirrus-scale-${ARG_COMPILER}-42001>|omp=<1>|arg=<--nodes=${ARG_NODES}>|arg=<--ntasks=${ARG_TASKS}>|arg=<--ntasks-per-node=288>|arg=<--cpus-per-task=1>|arg=<--hint=nomultithread>|arg=<--distribution=block:block>|arg=<--kill-on-bad-exit>|arg=<--unbuffered>|arg=<${ARG_REPOSITORY}/out/build/${ARG_CONFIGURE_PRESET}/parallel/parallel_src/tests/parhip_cube_scale_probe>|arg=<--side>|arg=<${ARG_SIDE}>|arg=<--expected-ranks>|arg=<${ARG_TASKS}>\n" + ) + set(${output_variable} "${log}" PARENT_SCOPE) +endfunction() + +function(run_runner_case) + set(options DEFAULT_SIDE SBATCH_SUBMISSION MISSING_SLURM_OUTPUT) + set( + one_value_arguments + NAME + RUNNER + SIDE + NODES + TASKS + TASKS_PER_NODE + CPUS_PER_TASK + HEAD + DIFF_STATUS + SRUN_STATUS + ) + cmake_parse_arguments(ARG "${options}" "${one_value_arguments}" "" ${ARGN}) + + foreach( + required_argument + IN ITEMS + NAME + RUNNER + NODES + TASKS + TASKS_PER_NODE + CPUS_PER_TASK + HEAD + DIFF_STATUS + SRUN_STATUS + ) + if(NOT DEFINED ARG_${required_argument}) + message(FATAL_ERROR "run_runner_case requires ${required_argument}") + endif() + endforeach() + if(NOT ARG_DEFAULT_SIDE AND NOT DEFINED ARG_SIDE) + message(FATAL_ERROR "run_runner_case requires SIDE or DEFAULT_SIDE") + endif() + + if(ARG_DEFAULT_SIDE) + set(runner_side 600) + else() + set(runner_side "${ARG_SIDE}") + endif() + if(ARG_DIFF_STATUS EQUAL 1) + set(runner_source_revision "${ARG_HEAD}-dirty") + else() + set(runner_source_revision "${ARG_HEAD}") + endif() + if(ARG_RUNNER STREQUAL "run-gnu-scale-probe.slurm") + set(runner_compiler gnu) + set(runner_configure_preset cirrus-gnu-tests) + set(runner_build_preset build-cirrus-gnu-tests) + elseif(ARG_RUNNER STREQUAL "run-cray-scale-probe.slurm") + set(runner_compiler cray) + set(runner_configure_preset cirrus-cray-tests) + set(runner_build_preset build-cirrus-cray-tests) + else() + message(FATAL_ERROR "unknown runner: ${ARG_RUNNER}") + endif() + + set(case_root "${TEST_ROOT}/${ARG_NAME}") + set(repository "${case_root}/KaHIP") + set(stub_directory "${case_root}/stubs") + set(log_path "${case_root}/commands.log") + set(source_runner "${SOURCE_DIR}/ci/cirrus/${ARG_RUNNER}") + set(copied_runner "${case_root}/slurm-spool/${ARG_RUNNER}") + set( + source_common_runner + "${SOURCE_DIR}/ci/cirrus/run-scale-probe-common.sh" + ) + set( + copied_common_runner + "${repository}/ci/cirrus/run-scale-probe-common.sh" + ) + + if(NOT EXISTS "${source_runner}") + message(FATAL_ERROR "runner is missing: ${source_runner}") + endif() + if(NOT EXISTS "${source_common_runner}") + message( + FATAL_ERROR + "common runner is missing: ${source_common_runner}" + ) + endif() + + file(REMOVE_RECURSE "${case_root}") + file( + MAKE_DIRECTORY + "${repository}/ci/cirrus" + "${case_root}/opt/catch2" + "${case_root}/slurm-spool" + ) + if(NOT ARG_MISSING_SLURM_OUTPUT) + file(MAKE_DIRECTORY "${repository}/out/slurm") + endif() + file(COPY_FILE "${source_runner}" "${copied_runner}") + file(COPY_FILE "${source_common_runner}" "${copied_common_runner}") + file(MAKE_DIRECTORY "${stub_directory}") + write_executable("${stub_directory}/module" "${module_stub}") + write_executable("${stub_directory}/git" "${git_stub}") + write_executable("${stub_directory}/cmake" "${cmake_stub}") + write_executable("${stub_directory}/srun" "${srun_stub}") + write_executable("${stub_directory}/sbatch" "${sbatch_stub}") + file(WRITE "${log_path}" "") + + set( + environment_arguments + --unset=BASH_FUNC_module%% + --unset=BASH_FUNC_ml%% + --unset=BASH_FUNC__module_raw%% + "PATH=${stub_directory}:$ENV{PATH}" + "KAHIP_STUB_LOG=${log_path}" + "KAHIP_STUB_REPO_ROOT=${repository}" + "KAHIP_STUB_HEAD=${ARG_HEAD}" + "KAHIP_STUB_DIFF_STATUS=${ARG_DIFF_STATUS}" + "KAHIP_STUB_SRUN_STATUS=${ARG_SRUN_STATUS}" + "KAHIP_STUB_CONFIGURE_PRESET=${runner_configure_preset}" + "KAHIP_STUB_BUILD_PRESET=${runner_build_preset}" + "KAHIP_STUB_CATCH2_PREFIX=${case_root}/opt/catch2" + "KAHIP_STUB_SOURCE_REVISION=${runner_source_revision}" + "KAHIP_STUB_SIDE=${runner_side}" + "KAHIP_STUB_NODES=${ARG_NODES}" + "KAHIP_STUB_TASKS=${ARG_TASKS}" + ) + if(ARG_SBATCH_SUBMISSION) + list( + APPEND environment_arguments + "KAHIP_STUB_BASH=${BASH_EXECUTABLE}" + "KAHIP_STUB_COMPILER=${runner_compiler}" + ) + set(runner_command "${stub_directory}/sbatch" "${copied_runner}") + else() + list( + APPEND environment_arguments + "SLURM_JOB_ID=42001" + "SLURM_JOB_NUM_NODES=${ARG_NODES}" + "SLURM_NTASKS=${ARG_TASKS}" + "SLURM_NTASKS_PER_NODE=${ARG_TASKS_PER_NODE}" + "SLURM_CPUS_PER_TASK=${ARG_CPUS_PER_TASK}" + ) + set(runner_command "${BASH_EXECUTABLE}" "${copied_runner}") + endif() + if(ARG_DEFAULT_SIDE) + list(APPEND environment_arguments --unset=KAHIP_SCALE_PROBE_SIDE) + else() + list( + APPEND environment_arguments + "KAHIP_SCALE_PROBE_SIDE=${ARG_SIDE}" + ) + endif() + + execute_process( + COMMAND + "${CMAKE_COMMAND}" -E env ${environment_arguments} + ${runner_command} + RESULT_VARIABLE runner_result + OUTPUT_VARIABLE runner_stdout + ERROR_VARIABLE runner_stderr + TIMEOUT 10 + WORKING_DIRECTORY "${repository}" + ) + file(READ "${log_path}" command_log) + + set(RUN_RESULT "${runner_result}" PARENT_SCOPE) + set(RUN_OUTPUT "${runner_stdout}\n${runner_stderr}" PARENT_SCOPE) + set(RUN_LOG "${command_log}" PARENT_SCOPE) + set(RUN_REPOSITORY "${repository}" PARENT_SCOPE) + set(RUN_CATCH2_PREFIX "${case_root}/opt/catch2" PARENT_SCOPE) + set( + RUN_SLURM_OUTPUT + "${repository}/out/slurm/kahip-${runner_compiler}-scale-42001.out" + PARENT_SCOPE + ) +endfunction() + +set(clean_head "0123456789abcdef0123456789abcdef01234567") +set(dirty_head "89abcdef0123456789abcdef0123456789abcdef") + +run_runner_case( + NAME gnu-default-clean + RUNNER run-gnu-scale-probe.slurm + DEFAULT_SIDE + SBATCH_SUBMISSION + NODES 8 + TASKS 2304 + TASKS_PER_NODE 288 + CPUS_PER_TASK 1 + HEAD "${clean_head}" + DIFF_STATUS 0 + SRUN_STATUS 0 +) +require_success("${RUN_RESULT}" "${RUN_OUTPUT}" "GNU default clean case") +if(NOT EXISTS "${RUN_SLURM_OUTPUT}") + message(FATAL_ERROR "stubbed Slurm did not pre-open ${RUN_SLURM_OUTPUT}") +endif() +require_contains( + "${RUN_OUTPUT}" + "compiler=gnu side=600 nodes=8 ranks=2304 bound=96562 source_revision=${clean_head}" + "GNU default record" +) +require_contains("${RUN_LOG}" "module|pwd=<${RUN_REPOSITORY}>|" "GNU module cwd") +require_contains("${RUN_LOG}" "|arg=" "GNU module restore") +require_contains( + "${RUN_LOG}" + "|arg=|arg=" + "GNU programming environment" +) +require_contains( + "${RUN_LOG}" + "|arg=|arg=" + "GNU CMake module" +) +require_contains( + "${RUN_LOG}" + "git|pwd=<${RUN_REPOSITORY}>|tmpdir=<${RUN_REPOSITORY}/out/tmp/cirrus-scale-gnu-42001>|omp=<1>|arg=<-C>|arg=<${RUN_REPOSITORY}>|arg=|arg=<--show-toplevel>" + "GNU repository identity" +) +require_contains( + "${RUN_LOG}" + "|arg=|arg=<--verify>|arg=" + "GNU source revision" +) +require_contains( + "${RUN_LOG}" + "|arg=|arg=<--quiet>|arg=<--no-ext-diff>|arg=|arg=<-->" + "GNU tracked dirty check" +) +require_contains( + "${RUN_LOG}" + "cmake|pwd=<${RUN_REPOSITORY}>|tmpdir=<${RUN_REPOSITORY}/out/tmp/cirrus-scale-gnu-42001>|omp=<1>|arg=<--fresh>|arg=<--preset>|arg=|arg=<-DCMAKE_PREFIX_PATH:PATH=${RUN_CATCH2_PREFIX}>|arg=<-DKAHIP_SCALE_PROBE_SOURCE_REVISION=${clean_head}>" + "GNU configure contract" +) +require_contains( + "${RUN_LOG}" + "cmake|pwd=<${RUN_REPOSITORY}>|tmpdir=<${RUN_REPOSITORY}/out/tmp/cirrus-scale-gnu-42001>|omp=<1>|arg=<--build>|arg=<--preset>|arg=|arg=<--target>|arg=" + "GNU target-only build" +) +string( + REGEX MATCH + "cmake\\|[^\n]*arg=<--build>[^\n]*" + gnu_build_log + "${RUN_LOG}" +) +require_not_contains( + "${gnu_build_log}" + "CMAKE_PREFIX_PATH" + "Catch2 prefix must be configure-only" +) +require_contains( + "${RUN_LOG}" + "srun|pwd=<${RUN_REPOSITORY}>|tmpdir=<${RUN_REPOSITORY}/out/tmp/cirrus-scale-gnu-42001>|omp=<1>|arg=<--nodes=8>|arg=<--ntasks=2304>|arg=<--ntasks-per-node=288>|arg=<--cpus-per-task=1>|arg=<--hint=nomultithread>|arg=<--distribution=block:block>|arg=<--kill-on-bad-exit>|arg=<--unbuffered>|arg=<${RUN_REPOSITORY}/out/build/cirrus-gnu-tests/parallel/parallel_src/tests/parhip_cube_scale_probe>|arg=<--side>|arg=<600>|arg=<--expected-ranks>|arg=<2304>" + "GNU launch contract" +) +make_expected_runner_log( + expected_gnu_log + COMPILER gnu + MODULE ccs/gnu-2026-06 + CONFIGURE_PRESET cirrus-gnu-tests + BUILD_PRESET build-cirrus-gnu-tests + REPOSITORY "${RUN_REPOSITORY}" + CATCH2_PREFIX "${RUN_CATCH2_PREFIX}" + HEAD "${clean_head}" + SOURCE_REVISION "${clean_head}" + SIDE 600 + NODES 8 + TASKS 2304 +) +set( + expected_sbatch_log + "sbatch|job=|time=<02:00:00>|exclusive=<1>|nodes=<8>|tasks=<2304>|tasks-per-node=<288>|cpus-per-task=<1>|account=|partition=|qos=|chdir=|output=\n" +) +require_equal( + "${RUN_LOG}" + "${expected_sbatch_log}${expected_gnu_log}" + "exact GNU submission and runner trace" +) +require_contains( + "${RUN_OUTPUT}" "scale-probe-stdout-marker" "unfiltered GNU stdout" +) +require_contains( + "${RUN_OUTPUT}" "scale-probe-stderr-marker" "unfiltered GNU stderr" +) + +run_runner_case( + NAME cray-default-submission + RUNNER run-cray-scale-probe.slurm + DEFAULT_SIDE + SBATCH_SUBMISSION + NODES 8 + TASKS 2304 + TASKS_PER_NODE 288 + CPUS_PER_TASK 1 + HEAD "${clean_head}" + DIFF_STATUS 0 + SRUN_STATUS 0 +) +require_success( + "${RUN_RESULT}" "${RUN_OUTPUT}" "Cray default submission" +) +if(NOT EXISTS "${RUN_SLURM_OUTPUT}") + message(FATAL_ERROR "stubbed Slurm did not pre-open ${RUN_SLURM_OUTPUT}") +endif() +make_expected_runner_log( + expected_cray_default_log + COMPILER cray + MODULE PrgEnv-cray/8.6.0 + CONFIGURE_PRESET cirrus-cray-tests + BUILD_PRESET build-cirrus-cray-tests + REPOSITORY "${RUN_REPOSITORY}" + CATCH2_PREFIX "${RUN_CATCH2_PREFIX}" + HEAD "${clean_head}" + SOURCE_REVISION "${clean_head}" + SIDE 600 + NODES 8 + TASKS 2304 +) +set( + expected_cray_sbatch_log + "sbatch|job=|time=<02:00:00>|exclusive=<1>|nodes=<8>|tasks=<2304>|tasks-per-node=<288>|cpus-per-task=<1>|account=|partition=|qos=|chdir=|output=\n" +) +require_equal( + "${RUN_LOG}" + "${expected_cray_sbatch_log}${expected_cray_default_log}" + "exact Cray submission and runner trace" +) + +run_runner_case( + NAME missing-slurm-output-directory + RUNNER run-gnu-scale-probe.slurm + DEFAULT_SIDE + SBATCH_SUBMISSION + MISSING_SLURM_OUTPUT + NODES 8 + TASKS 2304 + TASKS_PER_NODE 288 + CPUS_PER_TASK 1 + HEAD "${clean_head}" + DIFF_STATUS 0 + SRUN_STATUS 0 +) +require_failure( + "${RUN_RESULT}" + "${RUN_OUTPUT}" + "missing pre-submit Slurm output directory" +) +if(NOT "${RUN_LOG}" STREQUAL "") + message( + FATAL_ERROR + "Slurm started a job before opening its configured output\n${RUN_LOG}" + ) +endif() + +run_runner_case( + NAME cray-largest-dirty + RUNNER run-cray-scale-probe.slurm + SIDE 1008 + NODES 38 + TASKS 10944 + TASKS_PER_NODE 288 + CPUS_PER_TASK 1 + HEAD "${dirty_head}" + DIFF_STATUS 1 + SRUN_STATUS 0 +) +require_success("${RUN_RESULT}" "${RUN_OUTPUT}" "Cray dirty case") +require_contains( + "${RUN_OUTPUT}" + "compiler=cray side=1008 nodes=38 ranks=10944 bound=96392 source_revision=${dirty_head}-dirty" + "Cray dirty record" +) +require_contains( + "${RUN_LOG}" + "|arg=|arg=" + "Cray programming environment" +) +require_contains( + "${RUN_LOG}" + "|arg=<--preset>|arg=|arg=<-DCMAKE_PREFIX_PATH:PATH=${RUN_CATCH2_PREFIX}>|arg=<-DKAHIP_SCALE_PROBE_SOURCE_REVISION=${dirty_head}-dirty>" + "Cray configure contract" +) +require_contains( + "${RUN_LOG}" + "|arg=<--preset>|arg=|arg=<--target>|arg=" + "Cray target-only build" +) +require_contains( + "${RUN_LOG}" + "srun|pwd=<${RUN_REPOSITORY}>|tmpdir=<${RUN_REPOSITORY}/out/tmp/cirrus-scale-cray-42001>|omp=<1>|arg=<--nodes=38>|arg=<--ntasks=10944>|arg=<--ntasks-per-node=288>|arg=<--cpus-per-task=1>|arg=<--hint=nomultithread>|arg=<--distribution=block:block>|arg=<--kill-on-bad-exit>|arg=<--unbuffered>|arg=<${RUN_REPOSITORY}/out/build/cirrus-cray-tests/parallel/parallel_src/tests/parhip_cube_scale_probe>|arg=<--side>|arg=<1008>|arg=<--expected-ranks>|arg=<10944>" + "Cray launch contract" +) +make_expected_runner_log( + expected_cray_log + COMPILER cray + MODULE PrgEnv-cray/8.6.0 + CONFIGURE_PRESET cirrus-cray-tests + BUILD_PRESET build-cirrus-cray-tests + REPOSITORY "${RUN_REPOSITORY}" + CATCH2_PREFIX "${RUN_CATCH2_PREFIX}" + HEAD "${dirty_head}" + SOURCE_REVISION "${dirty_head}-dirty" + SIDE 1008 + NODES 38 + TASKS 10944 +) +require_equal( + "${RUN_LOG}" "${expected_cray_log}" "exact Cray runner trace" +) + +foreach(tuple IN ITEMS "755,16,4608,96198" "900,27,7776,96562") + string(REPLACE "," ";" tuple_fields "${tuple}") + list(GET tuple_fields 0 tuple_side) + list(GET tuple_fields 1 tuple_nodes) + list(GET tuple_fields 2 tuple_tasks) + list(GET tuple_fields 3 tuple_bound) + run_runner_case( + NAME "gnu-tuple-${tuple_side}" + RUNNER run-gnu-scale-probe.slurm + SIDE "${tuple_side}" + NODES "${tuple_nodes}" + TASKS "${tuple_tasks}" + TASKS_PER_NODE 288 + CPUS_PER_TASK 1 + HEAD "${clean_head}" + DIFF_STATUS 0 + SRUN_STATUS 0 + ) + require_success( + "${RUN_RESULT}" "${RUN_OUTPUT}" "GNU tuple ${tuple_side}" + ) + require_contains( + "${RUN_OUTPUT}" + "side=${tuple_side} nodes=${tuple_nodes} ranks=${tuple_tasks} bound=${tuple_bound}" + "GNU tuple ${tuple_side} record" + ) + make_expected_runner_log( + expected_tuple_log + COMPILER gnu + MODULE ccs/gnu-2026-06 + CONFIGURE_PRESET cirrus-gnu-tests + BUILD_PRESET build-cirrus-gnu-tests + REPOSITORY "${RUN_REPOSITORY}" + CATCH2_PREFIX "${RUN_CATCH2_PREFIX}" + HEAD "${clean_head}" + SOURCE_REVISION "${clean_head}" + SIDE "${tuple_side}" + NODES "${tuple_nodes}" + TASKS "${tuple_tasks}" + ) + require_equal( + "${RUN_LOG}" + "${expected_tuple_log}" + "exact GNU tuple ${tuple_side} trace" + ) +endforeach() + +foreach( + invalid_case + IN ITEMS + "unsupported-side,601,8,2304,288,1" + "wrong-nodes,755,8,4608,288,1" + "wrong-ranks,755,16,2304,288,1" + "wrong-ranks-per-node,755,16,4608,144,1" + "wrong-cpus-per-rank,755,16,4608,288,2" +) + string(REPLACE "," ";" invalid_fields "${invalid_case}") + list(GET invalid_fields 0 invalid_name) + list(GET invalid_fields 1 invalid_side) + list(GET invalid_fields 2 invalid_nodes) + list(GET invalid_fields 3 invalid_tasks) + list(GET invalid_fields 4 invalid_tasks_per_node) + list(GET invalid_fields 5 invalid_cpus_per_task) + run_runner_case( + NAME "invalid-${invalid_name}" + RUNNER run-gnu-scale-probe.slurm + SIDE "${invalid_side}" + NODES "${invalid_nodes}" + TASKS "${invalid_tasks}" + TASKS_PER_NODE "${invalid_tasks_per_node}" + CPUS_PER_TASK "${invalid_cpus_per_task}" + HEAD "${clean_head}" + DIFF_STATUS 0 + SRUN_STATUS 0 + ) + require_failure( + "${RUN_RESULT}" "${RUN_OUTPUT}" "invalid case ${invalid_name}" + ) + if(EXISTS "${TEST_ROOT}/invalid-${invalid_name}/commands.log") + file( + READ + "${TEST_ROOT}/invalid-${invalid_name}/commands.log" + invalid_log + ) + if(NOT "${invalid_log}" STREQUAL "") + message( + FATAL_ERROR + "invalid case ${invalid_name} invoked tools\n${invalid_log}" + ) + endif() + endif() +endforeach() + +run_runner_case( + NAME invalid-source-revision + RUNNER run-gnu-scale-probe.slurm + DEFAULT_SIDE + NODES 8 + TASKS 2304 + TASKS_PER_NODE 288 + CPUS_PER_TASK 1 + HEAD unknown + DIFF_STATUS 0 + SRUN_STATUS 0 +) +require_failure( + "${RUN_RESULT}" "${RUN_OUTPUT}" "unknown source revision rejection" +) +require_not_contains( + "${RUN_LOG}" "cmake|" "unknown source revision reached configure" +) +require_not_contains( + "${RUN_LOG}" "srun|" "unknown source revision reached launcher" +) + +run_runner_case( + NAME git-inspection-failure + RUNNER run-gnu-scale-probe.slurm + DEFAULT_SIDE + NODES 8 + TASKS 2304 + TASKS_PER_NODE 288 + CPUS_PER_TASK 1 + HEAD "${clean_head}" + DIFF_STATUS 2 + SRUN_STATUS 0 +) +require_failure( + "${RUN_RESULT}" "${RUN_OUTPUT}" "Git dirty-inspection failure" +) +require_not_contains( + "${RUN_LOG}" "cmake|" "failed Git inspection reached configure" +) +require_not_contains( + "${RUN_LOG}" "srun|" "failed Git inspection reached launcher" +) + +run_runner_case( + NAME nonzero-launch + RUNNER run-cray-scale-probe.slurm + DEFAULT_SIDE + NODES 8 + TASKS 2304 + TASKS_PER_NODE 288 + CPUS_PER_TASK 1 + HEAD "${clean_head}" + DIFF_STATUS 0 + SRUN_STATUS 37 +) +if(NOT "${RUN_RESULT}" STREQUAL "37") + message( + FATAL_ERROR + "nonzero srun status was not preserved: got ${RUN_RESULT}\n${RUN_OUTPUT}" + ) +endif() +require_contains("${RUN_LOG}" "srun|" "nonzero launcher invocation") diff --git a/ci/cirrus/run-cray-scale-probe.slurm b/ci/cirrus/run-cray-scale-probe.slurm new file mode 100755 index 00000000..dcdccd71 --- /dev/null +++ b/ci/cirrus/run-cray-scale-probe.slurm @@ -0,0 +1,19 @@ +#!/bin/bash --login +#SBATCH --job-name=kahip-cray-scale +#SBATCH --time=02:00:00 +#SBATCH --exclusive +#SBATCH --nodes=8 +#SBATCH --ntasks=2304 +#SBATCH --ntasks-per-node=288 +#SBATCH --cpus-per-task=1 +#SBATCH --account=e609 +#SBATCH --partition=standard +#SBATCH --qos=standard +#SBATCH --chdir=/work/e609/e609/eriche609/KaHIP +#SBATCH --output=/work/e609/e609/eriche609/KaHIP/out/slurm/%x-%j.out + +set -euo pipefail + +source "${PWD}/ci/cirrus/run-scale-probe-common.sh" +kahip_run_scale_probe \ + cray PrgEnv-cray/8.6.0 cirrus-cray-tests build-cirrus-cray-tests diff --git a/ci/cirrus/run-cray-tests.slurm b/ci/cirrus/run-cray-tests.slurm new file mode 100755 index 00000000..c7a226bf --- /dev/null +++ b/ci/cirrus/run-cray-tests.slurm @@ -0,0 +1,31 @@ +#!/bin/bash --login +#SBATCH --job-name=kahip-cray-tests +#SBATCH --time=02:00:00 +#SBATCH --exclusive +#SBATCH --nodes=1 +#SBATCH --ntasks=5 +#SBATCH --cpus-per-task=1 +#SBATCH --account=e609 +#SBATCH --partition=standard +#SBATCH --qos=standard +#SBATCH --chdir=/work/e609/e609/eriche609/KaHIP +#SBATCH --output=/work/e609/e609/eriche609/KaHIP/out/slurm/%x-%j.out + +set -euo pipefail + +module restore +module load PrgEnv-cray/8.6.0 +module load cmake/4.1.2 + +export OMP_NUM_THREADS=1 + +readonly catch2_prefix=/work/e609/e609/eriche609/opt/catch2 +test -d "${catch2_prefix}" +command -v srun + +cmake --fresh --preset cirrus-cray-tests \ + "-DCMAKE_PREFIX_PATH:PATH=${catch2_prefix}" +cmake --build --preset build-cirrus-cray-tests +# Individual MPI tests use srun and its preflags from the configure preset. +# Keep CTest itself outside srun and execute one test/job step at a time. +ctest --preset test-cirrus-cray-tests --parallel 1 diff --git a/ci/cirrus/run-gnu-scale-probe.slurm b/ci/cirrus/run-gnu-scale-probe.slurm new file mode 100755 index 00000000..7dbd5609 --- /dev/null +++ b/ci/cirrus/run-gnu-scale-probe.slurm @@ -0,0 +1,19 @@ +#!/bin/bash --login +#SBATCH --job-name=kahip-gnu-scale +#SBATCH --time=02:00:00 +#SBATCH --exclusive +#SBATCH --nodes=8 +#SBATCH --ntasks=2304 +#SBATCH --ntasks-per-node=288 +#SBATCH --cpus-per-task=1 +#SBATCH --account=e609 +#SBATCH --partition=standard +#SBATCH --qos=standard +#SBATCH --chdir=/work/e609/e609/eriche609/KaHIP +#SBATCH --output=/work/e609/e609/eriche609/KaHIP/out/slurm/%x-%j.out + +set -euo pipefail + +source "${PWD}/ci/cirrus/run-scale-probe-common.sh" +kahip_run_scale_probe \ + gnu ccs/gnu-2026-06 cirrus-gnu-tests build-cirrus-gnu-tests diff --git a/ci/cirrus/run-gnu-tests.slurm b/ci/cirrus/run-gnu-tests.slurm new file mode 100755 index 00000000..9f71be3a --- /dev/null +++ b/ci/cirrus/run-gnu-tests.slurm @@ -0,0 +1,31 @@ +#!/bin/bash --login +#SBATCH --job-name=kahip-gnu-tests +#SBATCH --time=02:00:00 +#SBATCH --exclusive +#SBATCH --nodes=1 +#SBATCH --ntasks=5 +#SBATCH --cpus-per-task=1 +#SBATCH --account=e609 +#SBATCH --partition=standard +#SBATCH --qos=standard +#SBATCH --chdir=/work/e609/e609/eriche609/KaHIP +#SBATCH --output=/work/e609/e609/eriche609/KaHIP/out/slurm/%x-%j.out + +set -euo pipefail + +module restore +module load ccs/gnu-2026-06 +module load cmake/4.1.2 + +export OMP_NUM_THREADS=1 + +readonly catch2_prefix=/work/e609/e609/eriche609/opt/catch2 +test -d "${catch2_prefix}" +command -v srun + +cmake --fresh --preset cirrus-gnu-tests \ + "-DCMAKE_PREFIX_PATH:PATH=${catch2_prefix}" +cmake --build --preset build-cirrus-gnu-tests +# Individual MPI tests use srun and its preflags from the configure preset. +# Keep CTest itself outside srun and execute one test/job step at a time. +ctest --preset test-cirrus-gnu-tests --parallel 1 diff --git a/ci/cirrus/run-scale-probe-common.sh b/ci/cirrus/run-scale-probe-common.sh new file mode 100755 index 00000000..c512abd8 --- /dev/null +++ b/ci/cirrus/run-scale-probe-common.sh @@ -0,0 +1,157 @@ +#!/usr/bin/env bash + +kahip_scale_probe_die() { + printf 'KaHIP Cirrus scale runner: %s\n' "$*" >&2 + exit 2 +} + +kahip_run_scale_probe() { + if [[ $# -ne 4 ]]; then + kahip_scale_probe_die 'internal runner configuration is incomplete' + fi + + local -r compiler_name=$1 + local -r programming_environment_module=$2 + local -r configure_preset=$3 + local -r build_preset=$4 + local -r ranks_per_node=288 + local -r side=${KAHIP_SCALE_PROBE_SIDE:-600} + + local expected_nodes + local expected_ranks + local expected_bound + case "${side}" in + 600) + expected_nodes=8 + expected_ranks=2304 + expected_bound=96562 + ;; + 755) + expected_nodes=16 + expected_ranks=4608 + expected_bound=96198 + ;; + 900) + expected_nodes=27 + expected_ranks=7776 + expected_bound=96562 + ;; + 1008) + expected_nodes=38 + expected_ranks=10944 + expected_bound=96392 + ;; + *) + kahip_scale_probe_die \ + "unsupported KAHIP_SCALE_PROBE_SIDE '${side}'; expected 600, 755, 900, or 1008" + ;; + esac + + if [[ ${SLURM_JOB_NUM_NODES-} != "${expected_nodes}" ]]; then + kahip_scale_probe_die \ + "side ${side} requires SLURM_JOB_NUM_NODES=${expected_nodes}, got '${SLURM_JOB_NUM_NODES-}'" + fi + if [[ ${SLURM_NTASKS-} != "${expected_ranks}" ]]; then + kahip_scale_probe_die \ + "side ${side} requires SLURM_NTASKS=${expected_ranks}, got '${SLURM_NTASKS-}'" + fi + if [[ ${SLURM_NTASKS_PER_NODE-} != "${ranks_per_node}" ]]; then + kahip_scale_probe_die \ + "scale runs require SLURM_NTASKS_PER_NODE=${ranks_per_node}, got '${SLURM_NTASKS_PER_NODE-}'" + fi + if [[ ${SLURM_CPUS_PER_TASK-} != 1 ]]; then + kahip_scale_probe_die \ + "scale runs require SLURM_CPUS_PER_TASK=1, got '${SLURM_CPUS_PER_TASK-}'" + fi + if [[ ! ${SLURM_JOB_ID-} =~ ^[0-9]+$ ]]; then + kahip_scale_probe_die \ + "SLURM_JOB_ID must be a decimal job identifier, got '${SLURM_JOB_ID-}'" + fi + + local repository_root + repository_root=$(pwd -P) + readonly repository_root + local -r catch2_prefix="${repository_root%/*}/opt/catch2" + local -r temporary_directory="${repository_root}/out/tmp/cirrus-scale-${compiler_name}-${SLURM_JOB_ID}" + + mkdir -p "${repository_root}/out/slurm" "${temporary_directory}" + export TMPDIR="${temporary_directory}" + export OMP_NUM_THREADS=1 + cd -- "${repository_root}" || + kahip_scale_probe_die "cannot enter repository: ${repository_root}" + + module restore + module load "${programming_environment_module}" + module load cmake/4.1.2 + + if [[ ! -d ${catch2_prefix} ]]; then + kahip_scale_probe_die \ + "required Catch2 prefix is missing: ${catch2_prefix}" + fi + if ! command -v srun >/dev/null; then + kahip_scale_probe_die 'srun is unavailable in the selected environment' + fi + + local git_toplevel + git_toplevel=$(git -C "${repository_root}" rev-parse --show-toplevel) + if [[ ${git_toplevel} != "${repository_root}" ]]; then + kahip_scale_probe_die \ + "runner path is not the Git repository root: ${repository_root}" + fi + + local source_revision + source_revision=$( + git -C "${repository_root}" rev-parse --verify 'HEAD^{commit}' + ) + if [[ ! ${source_revision} =~ ^[0-9a-f]{40}([0-9a-f]{24})?$ ]]; then + kahip_scale_probe_die \ + "Git returned an invalid source revision: ${source_revision}" + fi + + local diff_status + if git -C "${repository_root}" diff --quiet --no-ext-diff HEAD --; then + diff_status=0 + else + diff_status=$? + fi + case "${diff_status}" in + 0) + ;; + 1) + source_revision="${source_revision}-dirty" + ;; + *) + kahip_scale_probe_die \ + "Git tracked-dirty inspection failed with status ${diff_status}" + ;; + esac + + cmake --fresh --preset "${configure_preset}" \ + "-DCMAKE_PREFIX_PATH:PATH=${catch2_prefix}" \ + "-DKAHIP_SCALE_PROBE_SOURCE_REVISION=${source_revision}" + cmake --build --preset "${build_preset}" \ + --target parhip_cube_scale_probe + + local -r probe="${repository_root}/out/build/${configure_preset}/parallel/parallel_src/tests/parhip_cube_scale_probe" + if [[ ! -x ${probe} ]]; then + kahip_scale_probe_die "scale probe was not built at ${probe}" + fi + + printf \ + 'KaHIP Cirrus scale probe: compiler=%s side=%s nodes=%s ranks=%s bound=%s source_revision=%s\n' \ + "${compiler_name}" "${side}" "${expected_nodes}" "${expected_ranks}" \ + "${expected_bound}" "${source_revision}" + + exec srun \ + "--nodes=${expected_nodes}" \ + "--ntasks=${expected_ranks}" \ + "--ntasks-per-node=${ranks_per_node}" \ + --cpus-per-task=1 \ + --hint=nomultithread \ + --distribution=block:block \ + --kill-on-bad-exit \ + --unbuffered \ + "${probe}" \ + --side "${side}" \ + --expected-ranks "${expected_ranks}" +} diff --git a/ci/performance/.gitignore b/ci/performance/.gitignore new file mode 100644 index 00000000..43ae0e2a --- /dev/null +++ b/ci/performance/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +*.py[cod] diff --git a/ci/performance/README.md b/ci/performance/README.md new file mode 100644 index 00000000..c184933b --- /dev/null +++ b/ci/performance/README.md @@ -0,0 +1,128 @@ +# ParHIP acceptance and performance harness + +`parhip_harness.py` compares a pristine upstream build with the current +candidate using paired, alternating runs. It is deliberately fail-closed: an +incomplete matrix, a failed verifier, mismatched rank placement, missing stage +records, missing per-rank RSS, missing collective-byte records, or missing +distributed-graph topology timing cannot be reported as acceptance. + +## Required build contract + +Both executables must come from equivalent Release builds with +`OPTIMIZED_OUTPUT=ON`. The harness reads each `CMakeCache.txt`, probes the +compiler, CMake, and MPI launcher, hashes the executable and linked MPI +libraries, and rejects differing compiler/MPI/flags/generator provenance. The +baseline source must be clean and exactly match `pinned_upstream_revision`. +The candidate may be dirty; its binary diff hash and an untracked-file manifest +are recorded before and after the run and must remain unchanged. + +Every configured configure, build, generator, MPI benchmark, verifier, and +version/provenance command is prefixed with `run_limited`. Builds are capped at +two jobs through the command validator and environment. Benchmark runs are +serial and alternate baseline/candidate, then candidate/baseline, to limit +order bias. + +## Configuration + +The input is JSON with `schema_version` 1. Paths are resolved relative to the +configuration file. MPI `postflags` must be empty because launcher-specific +postflag placement could put them after the per-rank wrapper and change +ParHIP's arguments. + +```json +{ + "schema_version": 1, + "pinned_upstream_revision": "0123456789abcdef0123456789abcdef01234567", + "run_limited": "/work/KaHIP/ci/run-limited", + "mpiexec": { + "executable": "mpiexec", + "numproc_flag": "-n", + "preflags": ["--bind-to", "core"], + "postflags": [] + }, + "variants": { + "baseline": { + "source_directory": "/work/kahip-upstream", + "build_directory": "/work/kahip-upstream-build", + "executable": "/work/kahip-upstream-build/parallel/parallel_src/parhip", + "configure_command": ["cmake", "-S", ".", "-B", "/work/kahip-upstream-build", "-DCMAKE_BUILD_TYPE=Release", "-DOPTIMIZED_OUTPUT=ON"], + "build_command": ["cmake", "--build", "/work/kahip-upstream-build", "--parallel", "2"] + }, + "candidate": { + "source_directory": "/work/KaHIP", + "build_directory": "/work/kahip-candidate-build", + "executable": "/work/kahip-candidate-build/parallel/parallel_src/parhip", + "configure_command": ["cmake", "-S", ".", "-B", "/work/kahip-candidate-build", "-DCMAKE_BUILD_TYPE=Release", "-DOPTIMIZED_OUTPUT=ON"], + "build_command": ["cmake", "--build", "/work/kahip-candidate-build", "--parallel", "2"] + } + }, + "fixtures": [ + { + "name": "cube100", + "dimensions": [100, 100, 100], + "generator": "/work/kahip-candidate-build/parallel/parallel_src/tests/kahip_cube_generator", + "verifier": "/work/kahip-candidate-build/parallel/parallel_src/tests/kahip_cube_partition_verify" + } + ], + "matrix": { + "seeds": [1, 2, 3, 4, 5], + "ranks": [2, 4], + "blocks": [4], + "preconfigurations": ["fastmesh"], + "imbalance_percent": [3], + "repetitions": 5 + }, + "bootstrap": {"iterations": 10000, "seed": 1729, "min_pairs": 20}, + "collective_bytes_interposer": "/work/libkahip_pmpi_collective_bytes.so", + "output_directory": "/work/results/kahip-acceptance-001", + "timeout_seconds": 7200, + "concurrency": 2, + "environment": {} +} +``` + +Use `--prepare` to run the configured build commands; omit it for existing +builds: + +```shell +ci/run-limited python3 -m ci.performance.parhip_harness \ + --config /work/kahip-acceptance.json --prepare +``` + +The outer invocation is scoped as well as every operation launched by the +harness. The harness refuses an existing output directory so fixed-name +`tmppartition.txtp` files can never be stale. + +## Results and gates + +`events.jsonl` is fsynced after each fixture/run, raw stdout and stderr are +retained and hashed, and `results.json` is written atomically. Each partition +is independently checked by `kahip_cube_partition_verify`; its vertex count, +block count, balance, and weighted cut must agree with the case and ParHIP's +self-reported cut. + +Cut quality uses the median across repetitions for each paired seed. Aggregate +cut may regress by at most 1%, and no graph/configuration median may regress by +more than 3%. Runtime and maximum per-rank RSS use paired bootstrap resampling +of whole samples and compute `median(candidate*) / median(baseline*)`; the 95% +upper confidence bound must be at most 1.05. Hostname and CPU-affinity maps must +match rank by rank. + +The external PMPI instrument records `MPI_Dist_graph_create` and +`MPI_Dist_graph_create_adjacent` call counts and monotonic wall time separately +for every rank. This measures the MPI topology constructor itself without +adding mutable timing state to either KaHIP binary. The result reports summed +rank time, the maximum rank time, and the constructor counts for baseline and +candidate; topology timing is a reporting requirement, while the paired +end-to-end confidence interval remains the performance gate. + +## Synthetic tests + +The tests do not build or benchmark KaHIP: + +```shell +ci/run-limited python3 -m unittest discover -s ci/performance/tests -v +``` + +See `pmpi-collective-bytes.md` for the independently built PMPI measurement +library and its exact accounting boundary. diff --git a/ci/performance/__init__.py b/ci/performance/__init__.py new file mode 100644 index 00000000..d3140933 --- /dev/null +++ b/ci/performance/__init__.py @@ -0,0 +1 @@ +"""Standalone ParHIP acceptance and performance tooling.""" diff --git a/ci/performance/parhip_harness.py b/ci/performance/parhip_harness.py new file mode 100644 index 00000000..0990839e --- /dev/null +++ b/ci/performance/parhip_harness.py @@ -0,0 +1,2395 @@ +"""Acceptance-quality ParHIP comparison harness. + +The module keeps parsing, scheduling, and statistical decisions pure so they +can be pressure-tested without compiling or running ParHIP. The executable +orchestrator is defined below those independently testable functions. +""" + +from __future__ import annotations + +import argparse +from collections import defaultdict +import copy +from dataclasses import dataclass +from datetime import datetime, timezone +from fractions import Fraction +import hashlib +import itertools +import json +import math +import os +from pathlib import Path +import platform +import random +import re +import shutil +import signal +import socket +import statistics +import subprocess +import sys +import time +from typing import Any, Callable, Iterable, Mapping, Sequence + + +class ParseError(ValueError): + """Raised when a tool emits a noncanonical result record.""" + + +@dataclass(frozen=True) +class CommandResult: + return_code: int + stdout: str + stderr: str + elapsed_seconds: float + + +CommandExecutor = Callable[..., CommandResult] + + +_FLOAT = r"(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?" +_LEVEL_STAGE = re.compile( + rf"^log>cycle:\s*([0-9]+)\s+level:\s*(-?[0-9]+)\s+" + rf"(parallel label compression|contraction|projection|" + rf"label compression refinement)\s+took\s+({_FLOAT})\s*$" +) +_CYCLE_STAGE = re.compile( + rf"^log>cycle:\s*([0-9]+)\s+" + rf"(coarsening|initial partitioning|uncoarsening)\s+took\s+({_FLOAT})\s*$" +) +_STAGE_NAMES = { + "parallel label compression": "label_compression_coarsening", + "contraction": "contraction", + "coarsening": "coarsening_total", + "initial partitioning": "initial_partitioning", + "projection": "projection", + "label compression refinement": "label_compression_refinement", + "uncoarsening": "uncoarsening", +} +_PARTITION_TOTAL = re.compile( + rf"^log>total partitioning time elapsed\s+({_FLOAT})\s*$" +) +_FINAL_CUT = re.compile(r"^log>final edge cut\s+([0-9]+)\s*$") +_FINAL_BALANCE = re.compile(rf"^log>final balance\s+({_FLOAT})\s*$") +_DUMMY = re.compile(rf"^running collective dummy operations took\s+({_FLOAT})\s*$") +_BARE_TOOK = re.compile(rf"^took\s+({_FLOAT})\s*$") +_VERIFIER = re.compile( + r"^verified vertices=([0-9]+) blocks=([0-9]+) " + r"maximum-block-weight=([0-9]+) block-weights=\[([0-9]+(?:,[0-9]+)*)\] " + r"weighted-cut=([0-9]+)$" +) + + +def _finite_float(text: str, context: str) -> float: + value = float(text) + if not math.isfinite(value) or value < 0: + raise ParseError(f"{context} is not a finite nonnegative number") + return value + + +def parse_parhip_output(output: str) -> dict[str, Any]: + events: list[dict[str, Any]] = [] + totals: defaultdict[str, float] = defaultdict(float) + partition_seconds: float | None = None + final_cut: int | None = None + final_balance: float | None = None + dummy_seconds: float | None = None + input_elapsed: float | None = None + + for line in output.splitlines(): + if match := _LEVEL_STAGE.fullmatch(line): + cycle, level, raw_stage, seconds = match.groups() + stage = _STAGE_NAMES[raw_stage] + value = _finite_float(seconds, f"{stage} duration") + event = { + "cycle": int(cycle), + "level": int(level), + "stage": stage, + "seconds": value, + } + events.append(event) + totals[stage] += value + continue + if match := _CYCLE_STAGE.fullmatch(line): + cycle, raw_stage, seconds = match.groups() + stage = _STAGE_NAMES[raw_stage] + value = _finite_float(seconds, f"{stage} duration") + events.append( + { + "cycle": int(cycle), + "level": None, + "stage": stage, + "seconds": value, + } + ) + totals[stage] += value + continue + if match := _PARTITION_TOTAL.fullmatch(line): + if partition_seconds is not None: + raise ParseError("duplicate total partitioning time") + partition_seconds = _finite_float(match.group(1), "partition duration") + continue + if match := _FINAL_CUT.fullmatch(line): + if final_cut is not None: + raise ParseError("duplicate final edge cut") + final_cut = int(match.group(1)) + continue + if match := _FINAL_BALANCE.fullmatch(line): + if final_balance is not None: + raise ParseError("duplicate final balance") + final_balance = _finite_float(match.group(1), "final balance") + continue + if match := _DUMMY.fullmatch(line): + if dummy_seconds is not None: + raise ParseError("duplicate dummy-operation duration") + dummy_seconds = _finite_float(match.group(1), "dummy-operation duration") + continue + if match := _BARE_TOOK.fullmatch(line): + if input_elapsed is not None: + raise ParseError("duplicate input-ready elapsed time") + input_elapsed = _finite_float(match.group(1), "input-ready elapsed time") + + if partition_seconds is None or final_cut is None or final_balance is None: + raise ParseError("missing final partition metrics") + required_stages = {"coarsening_total", "initial_partitioning", "uncoarsening"} + missing_stages = sorted(required_stages.difference(totals)) + if missing_stages: + raise ParseError( + "missing required stage timing records " + f"({', '.join(missing_stages)}); use Release with OPTIMIZED_OUTPUT=ON" + ) + return { + "partition_seconds": partition_seconds, + "final_cut": final_cut, + "final_balance": final_balance, + "startup_dummy_seconds": dummy_seconds, + "input_ready_elapsed_seconds": input_elapsed, + "stage_events": events, + "stage_totals_seconds": dict(sorted(totals.items())), + } + + +def parse_verifier_output(output: str) -> dict[str, Any]: + record = output.strip() + match = _VERIFIER.fullmatch(record) + if match is None: + raise ParseError("partition verifier emitted a noncanonical record") + vertices, blocks, maximum, weights, cut = match.groups() + parsed_weights = [int(weight) for weight in weights.split(",")] + if len(parsed_weights) != int(blocks): + raise ParseError("partition verifier block-weight count is inconsistent") + return { + "balanced": True, + "vertices": int(vertices), + "blocks": int(blocks), + "maximum_block_weight": int(maximum), + "block_weights": parsed_weights, + "weighted_cut": int(cut), + } + + +def alternating_variant_order(pair_index: int) -> tuple[str, str]: + if pair_index < 0: + raise ValueError("pair index must be nonnegative") + return ( + ("baseline", "candidate") + if pair_index % 2 == 0 + else ("candidate", "baseline") + ) + + +def limited_command(wrapper: Path, command: Sequence[str]) -> list[str]: + if not command: + raise ValueError("limited command must not be empty") + return [str(wrapper), *map(str, command)] + + +def run_external_command( + command: list[str], + *, + cwd: Path, + environment: Mapping[str, str], + timeout_seconds: float, +) -> CommandResult: + if not command: + raise ValueError("external command must not be empty") + started_ns = time.monotonic_ns() + process = subprocess.Popen( + command, + cwd=cwd, + env=dict(environment), + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + encoding="utf-8", + errors="replace", + start_new_session=True, + ) + timed_out = False + try: + stdout, stderr = process.communicate(timeout=timeout_seconds) + except subprocess.TimeoutExpired: + timed_out = True + try: + os.killpg(process.pid, signal.SIGTERM) + except ProcessLookupError: + pass + try: + stdout, stderr = process.communicate(timeout=5) + except subprocess.TimeoutExpired: + try: + os.killpg(process.pid, signal.SIGKILL) + except ProcessLookupError: + pass + stdout, stderr = process.communicate() + elapsed_seconds = (time.monotonic_ns() - started_ns) / 1_000_000_000 + if timed_out: + stderr += ( + f"\nacceptance harness timeout after {timeout_seconds:g} seconds\n" + ) + return_code = 124 + else: + return_code = process.returncode + return CommandResult( + return_code=return_code, + stdout=stdout, + stderr=stderr, + elapsed_seconds=elapsed_seconds, + ) + + +def build_benchmark_command( + *, + run_limited: Path, + mpiexec: Path, + numproc_flag: str, + mpi_preflags: Sequence[str], + python_executable: Path, + rank_runner: Path, + rank_metrics_directory: Path, + parhip: Path, + graph: Path, + case: Mapping[str, Any], + collective_bytes_interposer: Path | None = None, +) -> list[str]: + """Build the exact launcher command for one paired benchmark sample. + + The rank wrapper belongs *after* the MPI launcher: one wrapper process is + created for each rank and can therefore use ``wait4`` to measure that + rank's ParHIP child. MPI postflags are deliberately absent from this API; + their placement is launcher-specific and can leak into ParHIP's argv. + """ + + ranks = _positive_integer(case.get("ranks"), "case ranks") + blocks = _positive_integer(case.get("blocks"), "case blocks") + seed = _nonnegative_integer(case.get("seed"), "case seed") + imbalance = _nonnegative_integer( + case.get("imbalance_percent"), "case imbalance_percent" + ) + preconfiguration = case.get("preconfiguration") + if not isinstance(preconfiguration, str) or not preconfiguration: + raise ValueError("case preconfiguration must be a nonempty string") + if not numproc_flag: + raise ValueError("MPI process-count flag must not be empty") + if any(not isinstance(flag, str) or not flag for flag in mpi_preflags): + raise ValueError("MPI preflags must be nonempty strings") + + rank_arguments = [ + str(mpiexec), + numproc_flag, + str(ranks), + *mpi_preflags, + str(python_executable), + str(rank_runner), + "--metrics-directory", + str(rank_metrics_directory), + ] + if collective_bytes_interposer is not None: + rank_arguments.extend(["--preload", str(collective_bytes_interposer)]) + rank_arguments.extend( + [ + "--", + str(parhip), + str(graph), + f"--k={blocks}", + f"--preconfiguration={preconfiguration}", + f"--seed={seed}", + f"--imbalance={imbalance}", + "--save_partition", + ] + ) + return limited_command(run_limited, rank_arguments) + + +def _integer(value: Any, name: str) -> int: + if isinstance(value, bool) or not isinstance(value, int): + raise ValueError(f"{name} must be an integer") + return value + + +def _positive_integer(value: Any, name: str) -> int: + result = _integer(value, name) + if result <= 0: + raise ValueError(f"{name} must be positive") + return result + + +def _nonnegative_integer(value: Any, name: str) -> int: + result = _integer(value, name) + if result < 0: + raise ValueError(f"{name} must be nonnegative") + return result + + +def _mapping(parent: Mapping[str, Any], name: str) -> Mapping[str, Any]: + value = parent.get(name) + if not isinstance(value, Mapping): + raise ValueError(f"{name} must be an object") + return value + + +def _nonempty_string(parent: Mapping[str, Any], name: str) -> str: + value = parent.get(name) + if not isinstance(value, str) or not value: + raise ValueError(f"{name} must be a nonempty string") + return value + + +def _string_list(parent: Mapping[str, Any], name: str, *, empty: bool) -> list[str]: + value = parent.get(name) + if not isinstance(value, list) or any( + not isinstance(item, str) or not item for item in value + ): + raise ValueError(f"{name} must be a list of nonempty strings") + if not empty and not value: + raise ValueError(f"{name} must not be empty") + return value + + +def _integer_list( + parent: Mapping[str, Any], name: str, *, positive: bool +) -> list[int]: + value = parent.get(name) + if not isinstance(value, list) or not value: + raise ValueError(f"{name} must be a nonempty list") + parser = _positive_integer if positive else _nonnegative_integer + return [parser(item, f"{name} item") for item in value] + + +def validate_config(config: Mapping[str, Any]) -> None: + if not isinstance(config, Mapping): + raise ValueError("configuration must be an object") + if config.get("schema_version") != 1: + raise ValueError("schema_version must be exactly 1") + pinned = _nonempty_string(config, "pinned_upstream_revision") + if re.fullmatch(r"[0-9a-f]{40}", pinned) is None: + raise ValueError("pinned_upstream_revision must be a full lowercase SHA-1") + _nonempty_string(config, "run_limited") + _nonempty_string(config, "output_directory") + if "python_executable" in config: + _nonempty_string(config, "python_executable") + if "collective_bytes_interposer" in config: + _nonempty_string(config, "collective_bytes_interposer") + if "environment" in config: + configured_environment = config["environment"] + if not isinstance(configured_environment, Mapping) or any( + not isinstance(name, str) + or not name + or not isinstance(value, str) + for name, value in configured_environment.items() + ): + raise ValueError("environment must map nonempty names to strings") + + concurrency = _positive_integer(config.get("concurrency", 2), "concurrency") + if concurrency > 2: + raise ValueError("concurrency must not exceed two") + timeout = config.get("timeout_seconds") + if isinstance(timeout, bool) or not isinstance(timeout, (int, float)) or timeout <= 0: + raise ValueError("timeout_seconds must be positive") + + mpi = _mapping(config, "mpiexec") + _nonempty_string(mpi, "executable") + _nonempty_string(mpi, "numproc_flag") + _string_list(mpi, "preflags", empty=True) + postflags = _string_list(mpi, "postflags", empty=True) + if postflags: + raise ValueError("mpiexec postflags must be empty; use preflags") + + variants = _mapping(config, "variants") + if set(variants) != {"baseline", "candidate"}: + raise ValueError("variants must contain exactly baseline and candidate") + for variant_name in ("baseline", "candidate"): + variant = variants[variant_name] + if not isinstance(variant, Mapping): + raise ValueError(f"{variant_name} variant must be an object") + for field in ("source_directory", "build_directory", "executable"): + _nonempty_string(variant, field) + configure_command = _string_list(variant, "configure_command", empty=False) + build_command = _string_list(variant, "build_command", empty=False) + validate_job_cap(configure_command, cap=concurrency) + explicit_build_cap = validate_job_cap(build_command, cap=concurrency) + build_launcher = Path(build_command[0]).name + environment_capped = ( + (build_launcher == "cmake" and "--build" in build_command) + or build_launcher in ("make", "gmake") + ) + if not explicit_build_cap and not environment_capped: + raise ValueError( + f"{variant_name} build_command requires explicit bounded parallelism" + ) + + fixtures = config.get("fixtures") + if not isinstance(fixtures, list) or not fixtures: + raise ValueError("fixtures must be a nonempty list") + fixture_names: set[str] = set() + for fixture in fixtures: + if not isinstance(fixture, Mapping): + raise ValueError("each fixture must be an object") + name = _nonempty_string(fixture, "name") + if re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9_.-]*", name) is None: + raise ValueError(f"fixture name is not path-safe: {name}") + if name in fixture_names: + raise ValueError(f"duplicate fixture name: {name}") + fixture_names.add(name) + dimensions = fixture.get("dimensions") + if not isinstance(dimensions, list) or len(dimensions) != 3: + raise ValueError(f"fixture {name} dimensions must contain nx, ny, nz") + for dimension in dimensions: + _positive_integer(dimension, f"fixture {name} dimension") + _nonempty_string(fixture, "verifier") + graph_path = fixture.get("graph_path") + if graph_path is None: + _nonempty_string(fixture, "generator") + elif not isinstance(graph_path, str) or not graph_path: + raise ValueError(f"fixture {name} graph_path must be a nonempty string") + + matrix = _mapping(config, "matrix") + _integer_list(matrix, "seeds", positive=False) + _integer_list(matrix, "ranks", positive=True) + _integer_list(matrix, "blocks", positive=True) + _integer_list(matrix, "imbalance_percent", positive=False) + _string_list(matrix, "preconfigurations", empty=False) + repetitions = _positive_integer(matrix.get("repetitions"), "matrix repetitions") + if repetitions < 2: + raise ValueError("matrix repetitions must be at least two") + + bootstrap = _mapping(config, "bootstrap") + _positive_integer(bootstrap.get("iterations"), "bootstrap iterations") + _nonnegative_integer(bootstrap.get("seed"), "bootstrap seed") + minimum_pairs = _positive_integer( + bootstrap.get("min_pairs"), "bootstrap min_pairs" + ) + if minimum_pairs < 2: + raise ValueError("bootstrap min_pairs must be at least two") + planned_pairs = ( + len(fixtures) + * len(matrix["seeds"]) + * len(matrix["ranks"]) + * len(matrix["blocks"]) + * len(matrix["preconfigurations"]) + * len(matrix["imbalance_percent"]) + * repetitions + ) + if minimum_pairs > planned_pairs: + raise ValueError("bootstrap min_pairs exceeds the planned paired matrix") + + +def validate_job_cap(command: Sequence[str], *, cap: int) -> bool: + _positive_integer(cap, "parallelism cap") + index = 0 + explicitly_bounded = False + while index < len(command): + argument = command[index] + requested: int | None = None + if argument in ("-j", "--parallel"): + if index + 1 >= len(command): + raise ValueError(f"unbounded parallelism flag {argument!r}") + index += 1 + try: + requested = int(command[index], 10) + except ValueError as error: + raise ValueError("parallelism must be an integer") from error + elif re.fullmatch(r"-j[0-9]+", argument): + requested = int(argument[2:], 10) + elif argument.startswith("--parallel="): + try: + requested = int(argument.partition("=")[2], 10) + except ValueError as error: + raise ValueError("parallelism must be an integer") from error + if requested is not None and (requested <= 0 or requested > cap): + raise ValueError( + f"requested parallelism {requested} exceeds configured cap {cap}" + ) + if requested is not None: + explicitly_bounded = True + index += 1 + return explicitly_bounded + + +def _resolved_path(base_directory: Path, value: str) -> str: + path = Path(value).expanduser() + if not path.is_absolute(): + path = base_directory / path + return str(path.resolve()) + + +def _resolved_executable(base_directory: Path, value: str) -> str: + if "/" in value or value.startswith("."): + return _resolved_path(base_directory, value) + located = shutil.which(value) + return str(Path(located).resolve()) if located is not None else value + + +def load_config(path: Path) -> dict[str, Any]: + path = path.resolve() + try: + raw = json.loads(path.read_text(encoding="utf-8")) + except json.JSONDecodeError as error: + raise ValueError(f"invalid JSON configuration {path}: {error}") from error + if not isinstance(raw, dict): + raise ValueError("configuration root must be an object") + validate_config(raw) + config = copy.deepcopy(raw) + base = path.parent + for name in ("run_limited", "output_directory"): + config[name] = _resolved_path(base, config[name]) + config["mpiexec"]["executable"] = _resolved_executable( + base, config["mpiexec"]["executable"] + ) + for variant in config["variants"].values(): + for name in ("source_directory", "build_directory", "executable"): + variant[name] = _resolved_path(base, variant[name]) + for fixture in config["fixtures"]: + for name in ("generator", "verifier", "graph_path"): + if name in fixture: + fixture[name] = _resolved_path(base, fixture[name]) + if "python_executable" in config: + config["python_executable"] = _resolved_executable( + base, config["python_executable"] + ) + else: + config["python_executable"] = str(Path(sys.executable).resolve()) + if "collective_bytes_interposer" in config: + config["collective_bytes_interposer"] = _resolved_path( + base, config["collective_bytes_interposer"] + ) + config["configuration_file"] = str(path) + return config + + +def benchmark_environment( + config: Mapping[str, Any], environment: Mapping[str, str] +) -> dict[str, str]: + result = dict(environment) + configured = config.get("environment", {}) + if not isinstance(configured, Mapping) or any( + not isinstance(name, str) + or not name + or not isinstance(value, str) + for name, value in configured.items() + ): + raise ValueError("environment must map nonempty names to strings") + result.update(configured) + concurrency = str(_positive_integer(config.get("concurrency", 2), "concurrency")) + result["CMAKE_BUILD_PARALLEL_LEVEL"] = concurrency + result["VCPKG_MAX_CONCURRENCY"] = concurrency + result["MAKEFLAGS"] = f"-j{concurrency}" + return result + + +def enumerate_cases(config: Mapping[str, Any]) -> list[dict[str, Any]]: + validate_config(config) + matrix = config["matrix"] + repetitions = range(matrix["repetitions"]) + cases: list[dict[str, Any]] = [] + for fixture, ranks, blocks, preconfiguration, imbalance, seed, repetition in itertools.product( + (entry["name"] for entry in config["fixtures"]), + matrix["ranks"], + matrix["blocks"], + matrix["preconfigurations"], + matrix["imbalance_percent"], + matrix["seeds"], + repetitions, + ): + cases.append( + { + "fixture": fixture, + "ranks": ranks, + "blocks": blocks, + "preconfiguration": preconfiguration, + "imbalance_percent": imbalance, + "seed": seed, + "repetition": repetition, + } + ) + return cases + + +def _percentile(sorted_values: Sequence[float], probability: float) -> float: + if not sorted_values: + raise ValueError("percentile requires at least one value") + position = (len(sorted_values) - 1) * probability + lower = math.floor(position) + upper = math.ceil(position) + if lower == upper: + return sorted_values[lower] + fraction = position - lower + return sorted_values[lower] * (1.0 - fraction) + sorted_values[upper] * fraction + + +def _ratio_of_medians(pairs: Sequence[tuple[float, float]]) -> float: + baseline = statistics.median(pair[0] for pair in pairs) + candidate = statistics.median(pair[1] for pair in pairs) + if baseline <= 0: + raise ValueError("baseline timing/RSS median must be positive") + if candidate < 0 or not math.isfinite(baseline) or not math.isfinite(candidate): + raise ValueError("timing/RSS samples must be finite and nonnegative") + return candidate / baseline + + +def bootstrap_ratio_of_medians( + pairs: Sequence[tuple[float, float]], + *, + iterations: int, + seed: int, +) -> dict[str, float | int]: + if not pairs: + raise ValueError("bootstrap requires at least one paired sample") + if iterations <= 0: + raise ValueError("bootstrap iteration count must be positive") + normalized = [(float(left), float(right)) for left, right in pairs] + estimate = _ratio_of_medians(normalized) + generator = random.Random(seed) + count = len(normalized) + distribution = [] + for _ in range(iterations): + sample = [normalized[generator.randrange(count)] for _ in range(count)] + distribution.append(_ratio_of_medians(sample)) + distribution.sort() + return { + "method": "paired-percentile-bootstrap-ratio-of-medians", + "confidence": 0.95, + "iterations": iterations, + "seed": seed, + "sample_count": count, + "estimate": estimate, + "lower": _percentile(distribution, 0.025), + "upper": _percentile(distribution, 0.975), + } + + +_CASE_FIELDS = ( + "fixture", + "ranks", + "blocks", + "preconfiguration", + "imbalance_percent", + "seed", + "repetition", +) +_SEED_FIELDS = _CASE_FIELDS[:-1] +_CONFIGURATION_FIELDS = _SEED_FIELDS[:-1] + + +def _key(case: Mapping[str, Any], fields: Sequence[str]) -> tuple[Any, ...]: + try: + return tuple(case[field] for field in fields) + except KeyError as error: + raise ValueError(f"run case is missing {error.args[0]!r}") from error + + +def _median_fraction(values: Sequence[int | Fraction]) -> Fraction: + if not values: + raise ValueError("median requires at least one value") + ordered = sorted(Fraction(value) for value in values) + middle = len(ordered) // 2 + if len(ordered) % 2: + return ordered[middle] + return (ordered[middle - 1] + ordered[middle]) / 2 + + +def _finite_ratio(numerator: Fraction, denominator: Fraction) -> float | None: + if denominator == 0: + return 1.0 if numerator == 0 else None + return float(numerator / denominator) + + +def _cut_gate( + baseline: Fraction, candidate: Fraction, *, numerator: int, denominator: int +) -> dict[str, Any]: + passed = ( + candidate == 0 + if baseline == 0 + else candidate * denominator <= baseline * numerator + ) + return { + "passed": passed, + "limit": numerator / denominator, + "ratio": _finite_ratio(candidate, baseline), + "baseline": float(baseline), + "candidate": float(candidate), + } + + +def evaluate_acceptance( + records: Iterable[Mapping[str, Any]], + *, + bootstrap_iterations: int, + bootstrap_seed: int, + min_pairs: int, + expected_cases: Iterable[Mapping[str, Any]] | None = None, +) -> dict[str, Any]: + if min_pairs <= 0: + raise ValueError("minimum pair count must be positive") + paired: dict[tuple[Any, ...], dict[str, Mapping[str, Any]]] = {} + for record in records: + variant = record.get("variant") + if variant not in ("baseline", "candidate"): + raise ValueError("run variant must be baseline or candidate") + case = record.get("case") + if not isinstance(case, Mapping): + raise ValueError("run record is missing its case") + pair_key = _key(case, _CASE_FIELDS) + variants = paired.setdefault(pair_key, {}) + if variant in variants: + raise ValueError(f"duplicate {variant} run for pair {pair_key}") + variants[variant] = record + incomplete = [key for key, values in paired.items() if len(values) != 2] + if incomplete: + raise ValueError(f"unpaired benchmark records: {incomplete}") + + if expected_cases is not None: + planned_keys = [_key(case, _CASE_FIELDS) for case in expected_cases] + if len(set(planned_keys)) != len(planned_keys): + raise ValueError("planned benchmark matrix contains duplicate cases") + actual_keys = set(paired) + missing = sorted(set(planned_keys).difference(actual_keys)) + unplanned = sorted(actual_keys.difference(planned_keys)) + if missing: + raise ValueError(f"missing planned benchmark cases: {missing}") + if unplanned: + raise ValueError(f"unplanned benchmark cases: {unplanned}") + + ordered_pairs = sorted(paired.items(), key=lambda item: item[0]) + balanced = True + placement_mismatches = [] + + seed_cuts: dict[tuple[Any, ...], dict[str, list[int]]] = defaultdict( + lambda: {"baseline": [], "candidate": []} + ) + runtime_pairs: list[tuple[float, float]] = [] + rss_pairs: list[tuple[float, float]] = [] + for pair_key, variants in ordered_pairs: + seed_key = pair_key[:-1] + for variant in ("baseline", "candidate"): + verification = variants[variant].get("verification") + if not isinstance(verification, Mapping): + raise ValueError("run record is missing verification metrics") + if verification.get("balanced") is not True: + balanced = False + cut = verification.get("weighted_cut") + if isinstance(cut, bool) or not isinstance(cut, int) or cut < 0: + raise ValueError("verified cut must be a nonnegative integer") + parhip = variants[variant].get("parhip") + if parhip is not None: + if not isinstance(parhip, Mapping) or parhip.get("final_cut") != cut: + raise ValueError("ParHIP self-reported cut differs from verifier") + seed_cuts[seed_key][variant].append(cut) + baseline_placement = _placement_signature(variants["baseline"]) + candidate_placement = _placement_signature(variants["candidate"]) + if baseline_placement != candidate_placement: + placement_mismatches.append( + { + "case": dict(zip(_CASE_FIELDS, pair_key, strict=True)), + "baseline": baseline_placement, + "candidate": candidate_placement, + } + ) + baseline_runtime = _positive_finite_sample( + variants["baseline"].get("end_to_end_seconds"), + "baseline end-to-end time", + ) + candidate_runtime = _positive_finite_sample( + variants["candidate"].get("end_to_end_seconds"), + "candidate end-to-end time", + ) + runtime_pairs.append( + (baseline_runtime, candidate_runtime) + ) + baseline_rss = _positive_integer_sample( + variants["baseline"].get("max_rank_rss_bytes"), + "baseline peak RSS", + ) + candidate_rss = _positive_integer_sample( + variants["candidate"].get("max_rank_rss_bytes"), + "candidate peak RSS", + ) + rss_pairs.append( + (float(baseline_rss), float(candidate_rss)) + ) + + seed_medians: dict[tuple[Any, ...], dict[str, Fraction]] = {} + for seed_key, cuts in seed_cuts.items(): + seed_medians[seed_key] = { + variant: _median_fraction(values) for variant, values in cuts.items() + } + baseline_sum = sum( + (cuts["baseline"] for cuts in seed_medians.values()), Fraction(0) + ) + candidate_sum = sum( + (cuts["candidate"] for cuts in seed_medians.values()), Fraction(0) + ) + aggregate_cut = _cut_gate( + baseline_sum, candidate_sum, numerator=101, denominator=100 + ) + + grouped: dict[tuple[Any, ...], dict[str, list[Fraction]]] = defaultdict( + lambda: {"baseline": [], "candidate": []} + ) + for seed_key, cuts in seed_medians.items(): + configuration_key = seed_key[:-1] + for variant in ("baseline", "candidate"): + grouped[configuration_key][variant].append(cuts[variant]) + configuration_results = [] + for configuration_key, cuts in sorted(grouped.items()): + gate = _cut_gate( + _median_fraction(cuts["baseline"]), + _median_fraction(cuts["candidate"]), + numerator=103, + denominator=100, + ) + configuration_results.append( + { + "configuration": dict( + zip(_CONFIGURATION_FIELDS, configuration_key, strict=True) + ), + **gate, + } + ) + per_configuration_cut = { + "passed": all(result["passed"] for result in configuration_results), + "limit": 1.03, + "configurations": configuration_results, + } + + enough_pairs = len(ordered_pairs) >= min_pairs + if enough_pairs: + runtime_ci = bootstrap_ratio_of_medians( + runtime_pairs, + iterations=bootstrap_iterations, + seed=bootstrap_seed, + ) + rss_ci = bootstrap_ratio_of_medians( + rss_pairs, + iterations=bootstrap_iterations, + seed=bootstrap_seed + 1, + ) + runtime_gate = {**runtime_ci, "limit": 1.05, "passed": runtime_ci["upper"] <= 1.05} + rss_gate = {**rss_ci, "limit": 1.05, "passed": rss_ci["upper"] <= 1.05} + else: + runtime_gate = { + "passed": False, + "status": "insufficient-pairs", + "sample_count": len(ordered_pairs), + "required_pairs": min_pairs, + "limit": 1.05, + } + rss_gate = dict(runtime_gate) + + gates = { + "balanced_partitions": {"passed": balanced}, + "rank_placement": { + "passed": not placement_mismatches, + "mismatches": placement_mismatches, + }, + "aggregate_cut": aggregate_cut, + "per_configuration_cut": per_configuration_cut, + "runtime_ci": runtime_gate, + "rss_ci": rss_gate, + } + return { + "paired_run_count": len(ordered_pairs), + "paired_seed_count": len(seed_medians), + "gates": gates, + "passed": all(gate["passed"] for gate in gates.values()), + } + + +def _positive_finite_sample(value: Any, name: str) -> float: + if isinstance(value, bool) or not isinstance(value, (int, float)): + raise ValueError(f"{name} must be numeric") + result = float(value) + if not math.isfinite(result) or result <= 0: + raise ValueError(f"{name} must be finite and positive") + return result + + +def _positive_integer_sample(value: Any, name: str) -> int: + if isinstance(value, bool) or not isinstance(value, int) or value <= 0: + raise ValueError(f"{name} must be a positive integer") + return value + + +def _placement_signature(record: Mapping[str, Any]) -> list[dict[str, Any]]: + case = record.get("case") + if not isinstance(case, Mapping): + raise ValueError("run record is missing its case") + ranks = _positive_integer(case.get("ranks"), "case ranks") + placement = record.get("rank_placement") + if not isinstance(placement, list) or len(placement) != ranks: + raise ValueError("run record has incomplete rank placement") + by_rank: list[dict[str, Any] | None] = [None] * ranks + for item in placement: + if not isinstance(item, Mapping): + raise ValueError("rank placement entry must be an object") + rank = item.get("rank") + hostname = item.get("hostname") + affinity = item.get("cpu_affinity") + if isinstance(rank, bool) or not isinstance(rank, int) or not 0 <= rank < ranks: + raise ValueError("rank placement identity is invalid") + if not isinstance(hostname, str) or not hostname: + raise ValueError("rank placement hostname is invalid") + if not isinstance(affinity, list) or not affinity: + raise ValueError("rank placement CPU affinity is invalid") + cpus = [] + for cpu in affinity: + if isinstance(cpu, bool) or not isinstance(cpu, int) or cpu < 0: + raise ValueError("rank placement CPU affinity is invalid") + cpus.append(cpu) + if len(set(cpus)) != len(cpus): + raise ValueError("rank placement CPU affinity contains duplicates") + if by_rank[rank] is not None: + raise ValueError(f"duplicate placement for rank {rank}") + by_rank[rank] = { + "rank": rank, + "hostname": hostname, + "cpu_affinity": sorted(cpus), + } + if any(item is None for item in by_rank): + raise ValueError("rank placement identities are not contiguous") + return [dict(item) for item in by_rank if item is not None] + + +def read_rank_metrics(directory: Path, *, expected_ranks: int) -> dict[str, Any]: + if expected_ranks <= 0: + raise ValueError("expected rank count must be positive") + paths = sorted(directory.glob("rank-*.json")) + if len(paths) != expected_ranks: + raise ValueError( + f"found {len(paths)} rank metrics, expected {expected_ranks}" + ) + rss_by_rank: list[int | None] = [None] * expected_ranks + records = [] + for path in paths: + record = json.loads(path.read_text(encoding="utf-8")) + rank = record.get("rank") + rss = record.get("max_rss_bytes") + if not isinstance(rank, int) or not 0 <= rank < expected_ranks: + raise ValueError(f"invalid rank metric identity in {path}") + if rss_by_rank[rank] is not None: + raise ValueError(f"duplicate rank metric for rank {rank}") + if record.get("return_code") != 0: + raise ValueError(f"rank {rank} exited unsuccessfully") + if not isinstance(rss, int) or rss <= 0: + raise ValueError(f"rank {rank} has invalid peak RSS") + hostname = record.get("hostname") + affinity = record.get("cpu_affinity") + if not isinstance(hostname, str) or not hostname: + raise ValueError(f"rank {rank} has invalid hostname") + if not isinstance(affinity, list) or not affinity or any( + isinstance(cpu, bool) or not isinstance(cpu, int) or cpu < 0 + for cpu in affinity + ): + raise ValueError(f"rank {rank} has invalid CPU affinity") + rss_by_rank[rank] = rss + records.append(record) + if any(value is None for value in rss_by_rank): + raise ValueError("rank metrics are not contiguous") + rss_values = [int(value) for value in rss_by_rank] + return { + "max_rank_rss_bytes": max(rss_values), + "per_rank_rss_bytes": rss_values, + "rank_records": sorted(records, key=lambda record: record["rank"]), + "rank_placement": [ + { + "rank": record["rank"], + "hostname": record["hostname"], + "cpu_affinity": sorted(record["cpu_affinity"]), + } + for record in sorted(records, key=lambda record: record["rank"]) + ], + } + + +_COLLECTIVE_COUNTER_FIELDS = ( + "calls", + "sent_bytes", + "received_bytes", + "self_sent_bytes", + "self_received_bytes", +) +_TOPOLOGY_COUNTER_FIELDS = ("calls", "elapsed_nanoseconds") +_TOPOLOGY_OPERATIONS = { + "MPI_Dist_graph_create", + "MPI_Dist_graph_create_adjacent", +} + + +def _nonnegative_counter(value: Any, context: str) -> int: + if isinstance(value, bool) or not isinstance(value, int) or value < 0: + raise ValueError(f"{context} must be a nonnegative integer") + return value + + +def read_collective_metrics( + directory: Path, *, expected_ranks: int +) -> dict[str, Any]: + _positive_integer(expected_ranks, "expected rank count") + paths = sorted(directory.glob("rank-*.json")) + if len(paths) != expected_ranks: + raise ValueError( + f"found {len(paths)} collective records, expected {expected_ranks}" + ) + by_rank: list[dict[str, Any] | None] = [None] * expected_ranks + global_totals = {field: 0 for field in _COLLECTIVE_COUNTER_FIELDS} + global_topology_totals = { + field: 0 for field in _TOPOLOGY_COUNTER_FIELDS + } + max_rank_endpoint_bytes = 0 + max_rank_topology_setup_nanoseconds = 0 + for path in paths: + record = json.loads(path.read_text(encoding="utf-8")) + if not isinstance(record, Mapping) or record.get("schema_version") != 1: + raise ValueError(f"invalid collective record schema in {path}") + rank = record.get("rank") + if isinstance(rank, bool) or not isinstance(rank, int) or not 0 <= rank < expected_ranks: + raise ValueError(f"invalid collective rank in {path}") + if by_rank[rank] is not None: + raise ValueError(f"duplicate collective record for rank {rank}") + if record.get("complete") is not True or record.get("error") is not None: + raise ValueError(f"collective accounting is incomplete on rank {rank}") + if record.get("live_persistent_requests") != 0: + raise ValueError(f"rank {rank} retained persistent collective requests") + hostname = record.get("hostname") + pid = record.get("pid") + if not isinstance(hostname, str) or not hostname: + raise ValueError(f"rank {rank} has invalid collective hostname") + if isinstance(pid, bool) or not isinstance(pid, int) or pid <= 0: + raise ValueError(f"rank {rank} has invalid collective PID") + operations = record.get("operations") + totals = record.get("totals") + if not isinstance(operations, list) or not isinstance(totals, Mapping): + raise ValueError(f"rank {rank} has invalid collective counters") + recomputed = {field: 0 for field in _COLLECTIVE_COUNTER_FIELDS} + names = set() + for operation_record in operations: + if not isinstance(operation_record, Mapping): + raise ValueError(f"rank {rank} has invalid operation counter") + name = operation_record.get("name") + if not isinstance(name, str) or not name or name in names: + raise ValueError(f"rank {rank} has duplicate/invalid operation name") + names.add(name) + for field in _COLLECTIVE_COUNTER_FIELDS: + recomputed[field] += _nonnegative_counter( + operation_record.get(field), f"rank {rank} {name} {field}" + ) + normalized_totals = { + field: _nonnegative_counter( + totals.get(field), f"rank {rank} total {field}" + ) + for field in _COLLECTIVE_COUNTER_FIELDS + } + if recomputed != normalized_totals: + raise ValueError(f"rank {rank} collective totals are inconsistent") + + topology = record.get("topology_setup") + if not isinstance(topology, Mapping): + raise ValueError(f"rank {rank} has no topology setup timing") + topology_operations = topology.get("operations") + topology_totals = topology.get("totals") + if not isinstance(topology_operations, list) or not isinstance( + topology_totals, Mapping + ): + raise ValueError(f"rank {rank} has invalid topology setup timing") + recomputed_topology = { + field: 0 for field in _TOPOLOGY_COUNTER_FIELDS + } + topology_names = set() + normalized_topology_operations = [] + for operation_record in topology_operations: + if not isinstance(operation_record, Mapping): + raise ValueError( + f"rank {rank} has invalid topology operation timing" + ) + name = operation_record.get("name") + if ( + not isinstance(name, str) + or name not in _TOPOLOGY_OPERATIONS + or name in topology_names + ): + raise ValueError( + f"rank {rank} has duplicate/invalid topology operation" + ) + topology_names.add(name) + normalized_operation = {"name": name} + for field in _TOPOLOGY_COUNTER_FIELDS: + value = _nonnegative_counter( + operation_record.get(field), + f"rank {rank} {name} {field}", + ) + recomputed_topology[field] += value + normalized_operation[field] = value + normalized_topology_operations.append(normalized_operation) + if topology_names != _TOPOLOGY_OPERATIONS: + raise ValueError(f"rank {rank} topology operation set is incomplete") + normalized_topology_totals = { + field: _nonnegative_counter( + topology_totals.get(field), + f"rank {rank} topology total {field}", + ) + for field in _TOPOLOGY_COUNTER_FIELDS + } + if recomputed_topology != normalized_topology_totals: + raise ValueError( + f"rank {rank} topology setup totals are inconsistent" + ) + for field, value in normalized_totals.items(): + global_totals[field] += value + for field, value in normalized_topology_totals.items(): + global_topology_totals[field] += value + max_rank_endpoint_bytes = max( + max_rank_endpoint_bytes, + normalized_totals["sent_bytes"] + normalized_totals["received_bytes"], + ) + max_rank_topology_setup_nanoseconds = max( + max_rank_topology_setup_nanoseconds, + normalized_topology_totals["elapsed_nanoseconds"], + ) + normalized = dict(record) + normalized["totals"] = normalized_totals + normalized["topology_setup"] = { + "operations": normalized_topology_operations, + "totals": normalized_topology_totals, + } + by_rank[rank] = normalized + if any(record is None for record in by_rank): + raise ValueError("collective rank records are not contiguous") + return { + "status": "complete", + "per_rank": [dict(record) for record in by_rank if record is not None], + "global_calls": global_totals["calls"], + "global_sent_bytes": global_totals["sent_bytes"], + "global_received_bytes": global_totals["received_bytes"], + "global_self_sent_bytes": global_totals["self_sent_bytes"], + "global_self_received_bytes": global_totals["self_received_bytes"], + "max_rank_endpoint_bytes": max_rank_endpoint_bytes, + "global_topology_setup_calls": global_topology_totals["calls"], + "global_topology_setup_nanoseconds": global_topology_totals[ + "elapsed_nanoseconds" + ], + "max_rank_topology_setup_nanoseconds": ( + max_rank_topology_setup_nanoseconds + ), + } + + +def validate_partition_metrics( + *, + case: Mapping[str, Any], + fixture: Mapping[str, Any], + parhip: Mapping[str, Any], + verification: Mapping[str, Any], +) -> None: + dimensions = fixture.get("dimensions") + if not isinstance(dimensions, list) or len(dimensions) != 3: + raise ValueError("fixture dimensions are invalid") + expected_vertices = math.prod( + _positive_integer(value, "fixture dimension") for value in dimensions + ) + if verification.get("vertices") != expected_vertices: + raise ValueError("partition verifier vertex count differs from fixture") + if verification.get("blocks") != case.get("blocks"): + raise ValueError("partition verifier block count differs from case") + if parhip.get("final_cut") != verification.get("weighted_cut"): + raise ValueError("ParHIP self-reported cut differs from verifier") + if verification.get("balanced") is not True: + raise ValueError("partition verifier did not establish balance") + + +def create_run_directory(path: Path) -> None: + path.mkdir(parents=True, exist_ok=False) + + +def _write_text_atomic(path: Path, value: str) -> None: + temporary = path.with_name(f".{path.name}.{os.getpid()}.tmp") + with temporary.open("x", encoding="utf-8") as output: + output.write(value) + output.flush() + os.fsync(output.fileno()) + os.replace(temporary, path) + + +def _write_json_atomic(path: Path, value: Mapping[str, Any]) -> None: + _write_text_atomic( + path, + json.dumps(value, indent=2, sort_keys=True, allow_nan=False) + "\n", + ) + + +def _append_json_line(path: Path, value: Mapping[str, Any]) -> None: + with path.open("a", encoding="utf-8") as output: + json.dump(value, output, sort_keys=True, allow_nan=False) + output.write("\n") + output.flush() + os.fsync(output.fileno()) + + +def _run_logged_command( + command: Sequence[str], + *, + log_stem: Path, + cwd: Path, + environment: Mapping[str, str], + timeout_seconds: float, + executor: CommandExecutor, +) -> CommandResult: + result = executor( + list(command), + cwd=cwd, + environment=dict(environment), + timeout_seconds=timeout_seconds, + ) + _write_text_atomic(Path(f"{log_stem}.stdout.log"), result.stdout) + _write_text_atomic(Path(f"{log_stem}.stderr.log"), result.stderr) + if result.return_code != 0: + raise RuntimeError( + f"command failed with status {result.return_code}: {' '.join(command)}" + ) + return result + + +def prepare_builds( + config: Mapping[str, Any], + *, + output_directory: Path, + environment: Mapping[str, str], + executor: CommandExecutor, +) -> None: + setup_directory = output_directory / "setup" + setup_directory.mkdir() + run_limited = Path(config["run_limited"]) + timeout_seconds = float(config["timeout_seconds"]) + for variant_name in ("baseline", "candidate"): + variant = config["variants"][variant_name] + source_directory = Path(variant["source_directory"]) + for phase in ("configure", "build"): + bare_command = variant[f"{phase}_command"] + validate_job_cap(bare_command, cap=config.get("concurrency", 2)) + _run_logged_command( + limited_command(run_limited, bare_command), + log_stem=setup_directory / f"{variant_name}-{phase}", + cwd=source_directory, + environment=environment, + timeout_seconds=timeout_seconds, + executor=executor, + ) + + +def prepare_fixture_graphs( + config: Mapping[str, Any], + *, + output_directory: Path, + environment: Mapping[str, str], + executor: CommandExecutor, +) -> tuple[dict[str, Path], list[dict[str, Any]]]: + fixtures_directory = output_directory / "fixtures" + fixtures_directory.mkdir() + graphs: dict[str, Path] = {} + provenance = [] + run_limited = Path(config["run_limited"]) + timeout_seconds = float(config["timeout_seconds"]) + for fixture in config["fixtures"]: + name = fixture["name"] + if "graph_path" in fixture: + graph = Path(fixture["graph_path"]) + if not graph.is_file(): + raise ValueError(f"fixture graph does not exist: {graph}") + generator_command = None + else: + graph = fixtures_directory / f"{name}.graph" + generator_path = Path(fixture["generator"]) + if not generator_path.is_file(): + raise ValueError( + f"fixture generator does not exist: {generator_path}" + ) + generator_command = limited_command( + run_limited, + [ + str(generator_path), + *(str(value) for value in fixture["dimensions"]), + str(graph), + ], + ) + _run_logged_command( + generator_command, + log_stem=fixtures_directory / f"{name}-generator", + cwd=fixtures_directory, + environment=environment, + timeout_seconds=timeout_seconds, + executor=executor, + ) + if not graph.is_file(): + raise RuntimeError(f"cube generator did not produce {graph}") + graphs[name] = graph.resolve() + generator = fixture.get("generator") + if generator is not None and not Path(generator).is_file(): + raise ValueError(f"fixture generator does not exist: {generator}") + verifier = Path(fixture["verifier"]) + if not verifier.is_file(): + raise ValueError(f"fixture verifier does not exist: {verifier}") + provenance.append( + { + "name": name, + "dimensions": list(fixture["dimensions"]), + "graph": str(graph.resolve()), + "graph_sha256": _file_digest(graph), + "generator": generator, + "generator_sha256": ( + _file_digest(Path(generator)) if generator is not None else None + ), + "generator_command": generator_command, + "verifier": str(verifier.resolve()), + "verifier_sha256": _file_digest(verifier), + } + ) + return graphs, provenance + + +def execute_one_run( + *, + variant: str, + case: Mapping[str, Any], + fixture: Mapping[str, Any], + graph: Path, + parhip: Path, + run_directory: Path, + run_limited: Path, + mpiexec: Path, + numproc_flag: str, + mpi_preflags: Sequence[str], + python_executable: Path, + rank_runner: Path, + environment: Mapping[str, str], + timeout_seconds: float, + executor: CommandExecutor, + collective_bytes_interposer: Path | None = None, +) -> dict[str, Any]: + if variant not in ("baseline", "candidate"): + raise ValueError("run variant must be baseline or candidate") + create_run_directory(run_directory) + rank_metrics_directory = run_directory / "rank-metrics" + run_environment = dict(environment) + collective_directory: Path | None = None + if collective_bytes_interposer is not None: + if not collective_bytes_interposer.is_file(): + raise ValueError( + f"collective-byte interposer does not exist: {collective_bytes_interposer}" + ) + collective_directory = run_directory / "collective-bytes" + collective_directory.mkdir() + run_environment["KAHIP_PMPI_BYTES_DIRECTORY"] = str( + collective_directory.resolve() + ) + command = build_benchmark_command( + run_limited=run_limited, + mpiexec=mpiexec, + numproc_flag=numproc_flag, + mpi_preflags=mpi_preflags, + python_executable=python_executable, + rank_runner=rank_runner, + rank_metrics_directory=rank_metrics_directory, + parhip=parhip, + graph=graph, + case=case, + collective_bytes_interposer=collective_bytes_interposer, + ) + result = executor( + command, + cwd=run_directory, + environment=run_environment, + timeout_seconds=timeout_seconds, + ) + stdout_path = run_directory / "parhip.stdout.log" + stderr_path = run_directory / "parhip.stderr.log" + _write_text_atomic(stdout_path, result.stdout) + _write_text_atomic(stderr_path, result.stderr) + if result.return_code != 0: + raise RuntimeError( + f"{variant} ParHIP run failed with status {result.return_code}; " + f"see {stderr_path}" + ) + partition_path = run_directory / "tmppartition.txtp" + if not partition_path.is_file(): + raise RuntimeError(f"ParHIP did not produce {partition_path}") + + parhip_metrics = parse_parhip_output(result.stdout) + ranks = _positive_integer(case.get("ranks"), "case ranks") + rank_metrics = read_rank_metrics( + rank_metrics_directory, expected_ranks=ranks + ) + collective_metrics = ( + read_collective_metrics(collective_directory, expected_ranks=ranks) + if collective_directory is not None + else { + "status": "incomplete", + "reason": "no PMPI collective-byte interposer was configured", + } + ) + if collective_metrics.get("status") == "complete": + for placement, rank_record, collective_record in zip( + rank_metrics["rank_placement"], + rank_metrics["rank_records"], + collective_metrics["per_rank"], + strict=True, + ): + if collective_record["hostname"] != placement["hostname"]: + raise ValueError("collective/rank metric hostname differs") + child_pid = rank_record.get("child_pid") + if child_pid is not None and collective_record["pid"] != child_pid: + raise ValueError("collective record does not belong to rank child") + dimensions = fixture.get("dimensions") + if not isinstance(dimensions, list) or len(dimensions) != 3: + raise ValueError("fixture dimensions are invalid") + verifier = Path(_nonempty_string(fixture, "verifier")) + verifier_command = limited_command( + run_limited, + [ + str(verifier), + *(str(value) for value in dimensions), + str(case["blocks"]), + str(case["imbalance_percent"]), + str(partition_path), + ], + ) + verifier_result = executor( + verifier_command, + cwd=run_directory, + environment=dict(environment), + timeout_seconds=timeout_seconds, + ) + verifier_stdout_path = run_directory / "verifier.stdout.log" + verifier_stderr_path = run_directory / "verifier.stderr.log" + _write_text_atomic(verifier_stdout_path, verifier_result.stdout) + _write_text_atomic(verifier_stderr_path, verifier_result.stderr) + if verifier_result.return_code != 0: + raise RuntimeError( + f"partition verifier failed with status {verifier_result.return_code}; " + f"see {verifier_stderr_path}" + ) + verification = parse_verifier_output(verifier_result.stdout) + validate_partition_metrics( + case=case, + fixture=fixture, + parhip=parhip_metrics, + verification=verification, + ) + partition_sha256 = _file_digest(partition_path) + return { + "variant": variant, + "case": dict(case), + "end_to_end_seconds": _positive_finite_sample( + result.elapsed_seconds, "end-to-end time" + ), + "max_rank_rss_bytes": rank_metrics["max_rank_rss_bytes"], + "per_rank_rss_bytes": rank_metrics["per_rank_rss_bytes"], + "rank_placement": rank_metrics["rank_placement"], + "rank_metrics": rank_metrics["rank_records"], + "collective_bytes": collective_metrics, + "parhip": parhip_metrics, + "verification": verification, + "partition_sha256": partition_sha256, + "setup_timing": { + "startup_dummy_seconds": parhip_metrics["startup_dummy_seconds"], + "input_ready_elapsed_seconds": parhip_metrics[ + "input_ready_elapsed_seconds" + ], + }, + "topology_timing": ( + { + "status": "complete", + "measurement": ( + "PMPI distributed-graph construction wall time" + ), + "global_calls": collective_metrics[ + "global_topology_setup_calls" + ], + "global_rank_nanoseconds": collective_metrics[ + "global_topology_setup_nanoseconds" + ], + "max_rank_nanoseconds": collective_metrics[ + "max_rank_topology_setup_nanoseconds" + ], + } + if collective_metrics.get("status") == "complete" + else { + "status": "incomplete", + "reason": ( + "no complete PMPI distributed-graph topology timing " + "was supplied" + ), + } + ), + "commands": { + "benchmark": command, + "verifier": verifier_command, + }, + "artifacts": { + "run_directory": str(run_directory.resolve()), + "graph_sha256": _file_digest(graph), + "partition_sha256": partition_sha256, + "stdout_sha256": _file_digest(stdout_path), + "stderr_sha256": _file_digest(stderr_path), + "verifier_stdout_sha256": _file_digest(verifier_stdout_path), + "verifier_stderr_sha256": _file_digest(verifier_stderr_path), + }, + } + + +def validate_build_equivalence( + baseline: Mapping[str, Any], + candidate: Mapping[str, Any], + *, + require_stage_timings: bool, +) -> None: + required = ( + "build_type", + "optimized_output", + "compiler", + "compiler_version", + "release_flags", + "link_flags", + "cmake_generator", + "cmake_version", + "mpi_executable", + "mpi_version", + "mpi_cache_identity", + "linked_mpi_libraries", + ) + for name in required: + if name not in baseline or name not in candidate: + raise ValueError(f"build provenance is missing {name}") + if baseline["build_type"] != "Release" or candidate["build_type"] != "Release": + raise ValueError("performance comparison requires Release builds") + if require_stage_timings and ( + baseline["optimized_output"].upper() != "ON" + or candidate["optimized_output"].upper() != "ON" + ): + raise ValueError("stage timing requires OPTIMIZED_OUTPUT=ON") + mismatches = [name for name in required if baseline[name] != candidate[name]] + if mismatches: + raise ValueError( + "baseline and candidate build provenance differs: " + + ", ".join(mismatches) + ) + + +def parse_cmake_cache(path: Path) -> dict[str, str]: + values: dict[str, str] = {} + try: + lines = path.read_text(encoding="utf-8").splitlines() + except UnicodeDecodeError as error: + raise ValueError(f"CMake cache is not UTF-8: {path}") from error + for line_number, line in enumerate(lines, start=1): + if not line or line.startswith(("//", "#")): + continue + match = re.fullmatch(r"([^:=]+):[^=]*=(.*)", line) + if match is None: + continue + name, value = match.groups() + if name in values: + raise ValueError( + f"duplicate CMake cache entry {name!r} at line {line_number}" + ) + values[name] = value + return values + + +def _cache_value(cache: Mapping[str, str], name: str) -> str: + value = cache.get(name) + if not isinstance(value, str) or not value: + raise ValueError(f"CMake cache is missing {name}") + return value + + +def build_provenance_from_cache( + cache: Mapping[str, str], + *, + compiler_version: str, + mpi_version: str, + executable_sha256: str, + cmake_version: str, + linked_mpi_libraries: Sequence[Mapping[str, str]], +) -> dict[str, Any]: + if ( + not compiler_version.strip() + or not mpi_version.strip() + or not cmake_version.strip() + ): + raise ValueError("compiler, MPI, and CMake version records must not be empty") + if re.fullmatch(r"[0-9a-f]{64}", executable_sha256) is None: + raise ValueError("executable SHA-256 must be canonical lowercase hex") + common_flags = cache.get("CMAKE_CXX_FLAGS", "").strip() + release_flags = _cache_value(cache, "CMAKE_CXX_FLAGS_RELEASE").strip() + effective_flags = " ".join( + part for part in (common_flags, release_flags) if part + ) + common_link_flags = cache.get("CMAKE_EXE_LINKER_FLAGS", "").strip() + release_link_flags = cache.get("CMAKE_EXE_LINKER_FLAGS_RELEASE", "").strip() + effective_link_flags = " ".join( + part for part in (common_link_flags, release_link_flags) if part + ) + mpi_cache_identity = { + name: value + for name, value in sorted(cache.items()) + if name.startswith("MPI_") + and any( + token in name + for token in ("COMPILER", "INCLUDE", "LIBRARY", "LIBRARIES") + ) + } + return { + "build_type": _cache_value(cache, "CMAKE_BUILD_TYPE"), + "optimized_output": _cache_value(cache, "OPTIMIZED_OUTPUT"), + "compiler": _cache_value(cache, "CMAKE_CXX_COMPILER"), + "compiler_version": compiler_version.strip(), + "release_flags": effective_flags, + "link_flags": effective_link_flags, + "cmake_generator": _cache_value(cache, "CMAKE_GENERATOR"), + "cmake_version": cmake_version.strip(), + "mpi_executable": _cache_value(cache, "MPIEXEC_EXECUTABLE"), + "mpi_version": mpi_version.strip(), + "mpi_cache_identity": mpi_cache_identity, + "linked_mpi_libraries": [dict(record) for record in linked_mpi_libraries], + "executable_sha256": executable_sha256, + } + + +def _probe_version( + command: Sequence[str], + *, + run_limited: Path, + cwd: Path, + environment: Mapping[str, str], + timeout_seconds: float, + executor: CommandExecutor, +) -> str: + result = executor( + limited_command(run_limited, command), + cwd=cwd, + environment=dict(environment), + timeout_seconds=timeout_seconds, + ) + if result.return_code != 0: + raise RuntimeError( + f"version probe {command[0]!r} failed with status {result.return_code}" + ) + lines = [] + for line in (result.stdout + "\n" + result.stderr).splitlines(): + if line.startswith(("Running as unit:", "Finished with result:")): + continue + if line.strip(): + lines.append(line.rstrip()) + if not lines: + raise RuntimeError(f"version probe {command[0]!r} emitted no version") + return "\n".join(lines) + + +def _linked_mpi_libraries( + executable: Path, + *, + run_limited: Path, + cwd: Path, + environment: Mapping[str, str], + timeout_seconds: float, + executor: CommandExecutor, +) -> list[dict[str, str]]: + ldd = shutil.which("ldd") + if ldd is None: + raise RuntimeError("ldd is required to identify the linked MPI runtime") + result = executor( + limited_command(run_limited, [ldd, str(executable)]), + cwd=cwd, + environment=dict(environment), + timeout_seconds=timeout_seconds, + ) + if result.return_code != 0: + raise RuntimeError( + f"cannot inspect linked MPI libraries for {executable}: {result.stderr}" + ) + libraries: list[dict[str, str]] = [] + for line in result.stdout.splitlines(): + match = re.match(r"\s*(\S+)\s+=>\s+(\S+)\s+\(", line) + if match is None: + continue + name, raw_path = match.groups() + lowered = name.lower() + if not any(token in lowered for token in ("libmpi", "libmpich", "libpmix")): + continue + library = Path(raw_path) + if not library.is_file(): + raise RuntimeError(f"linked MPI library does not exist: {library}") + libraries.append( + { + "name": name, + "path": str(library.resolve()), + "sha256": _file_digest(library), + } + ) + if not libraries: + raise RuntimeError(f"no linked MPI library was found for {executable}") + return sorted(libraries, key=lambda record: (record["name"], record["path"])) + + +def collect_build_provenance( + variant: Mapping[str, Any], + *, + configured_mpiexec: Path, + run_limited: Path, + environment: Mapping[str, str], + timeout_seconds: float, + executor: CommandExecutor, +) -> dict[str, Any]: + build_directory = Path(_nonempty_string(variant, "build_directory")) + executable = Path(_nonempty_string(variant, "executable")) + if not executable.is_file(): + raise ValueError(f"ParHIP executable does not exist: {executable}") + cache = parse_cmake_cache(build_directory / "CMakeCache.txt") + compiler = Path(_cache_value(cache, "CMAKE_CXX_COMPILER")) + cached_mpiexec = Path(_cache_value(cache, "MPIEXEC_EXECUTABLE")) + if compiler.is_absolute(): + compiler_command = str(compiler) + else: + located_compiler = shutil.which(str(compiler)) + if located_compiler is None: + raise ValueError(f"cannot resolve configured compiler {compiler}") + compiler_command = located_compiler + configured_mpiexec_resolved = Path( + shutil.which(str(configured_mpiexec)) or configured_mpiexec + ).resolve() + cached_mpiexec_resolved = Path( + shutil.which(str(cached_mpiexec)) or cached_mpiexec + ).resolve() + if configured_mpiexec_resolved != cached_mpiexec_resolved: + raise ValueError( + "configured mpiexec differs from the executable recorded by CMake" + ) + compiler_version = _probe_version( + [compiler_command, "--version"], + run_limited=run_limited, + cwd=build_directory, + environment=environment, + timeout_seconds=timeout_seconds, + executor=executor, + ) + mpi_version = _probe_version( + [str(configured_mpiexec_resolved), "--version"], + run_limited=run_limited, + cwd=build_directory, + environment=environment, + timeout_seconds=timeout_seconds, + executor=executor, + ) + cmake = shutil.which("cmake") + if cmake is None: + raise RuntimeError("cmake is required for build provenance") + cmake_version = _probe_version( + [cmake, "--version"], + run_limited=run_limited, + cwd=build_directory, + environment=environment, + timeout_seconds=timeout_seconds, + executor=executor, + ) + provenance = build_provenance_from_cache( + cache, + compiler_version=compiler_version, + mpi_version=mpi_version, + executable_sha256=_file_digest(executable), + cmake_version=cmake_version, + linked_mpi_libraries=_linked_mpi_libraries( + executable, + run_limited=run_limited, + cwd=build_directory, + environment=environment, + timeout_seconds=timeout_seconds, + executor=executor, + ), + ) + provenance.update( + { + "source_directory": str( + Path(_nonempty_string(variant, "source_directory")).resolve() + ), + "build_directory": str(build_directory.resolve()), + "executable": str(executable.resolve()), + } + ) + return provenance + + +def _git(source_directory: Path, *arguments: str) -> bytes: + result = subprocess.run( + ["git", "-C", source_directory, *arguments], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + return result.stdout + + +def _file_digest(path: Path) -> str: + digest = hashlib.sha256() + if path.is_symlink(): + digest.update(b"symlink\0") + digest.update(os.readlink(path).encode("utf-8", errors="surrogateescape")) + return digest.hexdigest() + with path.open("rb") as source: + for chunk in iter(lambda: source.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def collect_git_provenance(source_directory: Path) -> dict[str, Any]: + source_directory = source_directory.resolve() + revision = _git(source_directory, "rev-parse", "HEAD").decode().strip() + diff = _git(source_directory, "diff", "--binary", "HEAD", "--") + tracked_names = [ + name + for name in _git(source_directory, "diff", "--name-only", "HEAD", "--") + .decode("utf-8", errors="surrogateescape") + .splitlines() + if name + ] + untracked_bytes = _git( + source_directory, "ls-files", "--others", "--exclude-standard", "-z" + ) + untracked = sorted( + item.decode("utf-8", errors="surrogateescape") + for item in untracked_bytes.split(b"\0") + if item + ) + manifest = hashlib.sha256() + untracked_records = [] + for relative in untracked: + encoded = relative.encode("utf-8", errors="surrogateescape") + full_path = source_directory / relative + digest = _file_digest(full_path) + size = full_path.lstat().st_size + manifest.update(len(encoded).to_bytes(8, "big")) + manifest.update(encoded) + manifest.update(size.to_bytes(8, "big")) + manifest.update(bytes.fromhex(digest)) + untracked_records.append( + {"path": relative, "size_bytes": size, "sha256": digest} + ) + return { + "source_directory": str(source_directory), + "revision": revision, + "clean": not diff and not untracked, + "tracked_changed_files": sorted(tracked_names), + "diff_sha256": hashlib.sha256(diff).hexdigest(), + "diff_bytes": len(diff), + "untracked_files": untracked, + "untracked_manifest": untracked_records, + "untracked_manifest_sha256": manifest.hexdigest() if untracked else None, + } + + +def validate_pristine_baseline( + provenance: Mapping[str, Any], pinned_revision: str +) -> None: + if provenance.get("revision") != pinned_revision: + raise ValueError( + "baseline revision does not match the pinned pristine upstream SHA" + ) + if not provenance.get("clean"): + raise ValueError("baseline source is not pristine") + + +_GIT_STABILITY_FIELDS = ( + "revision", + "clean", + "tracked_changed_files", + "diff_sha256", + "diff_bytes", + "untracked_files", + "untracked_manifest_sha256", +) + + +def validate_unchanged_git_provenance( + before: Mapping[str, Any], after: Mapping[str, Any], *, variant: str +) -> None: + changed = [name for name in _GIT_STABILITY_FIELDS if before.get(name) != after.get(name)] + if changed: + raise ValueError( + f"{variant} source changed during acceptance run: {', '.join(changed)}" + ) + + +def _read_optional_text(path: Path) -> str | None: + try: + return path.read_text(encoding="utf-8").strip() or None + except (OSError, UnicodeDecodeError): + return None + + +def machine_provenance(environment: Mapping[str, str]) -> dict[str, Any]: + affinity = ( + sorted(os.sched_getaffinity(0)) + if hasattr(os, "sched_getaffinity") + else list(range(os.cpu_count() or 1)) + ) + cpu_models: set[str] = set() + cpuinfo = _read_optional_text(Path("/proc/cpuinfo")) + if cpuinfo is not None: + for line in cpuinfo.splitlines(): + if line.lower().startswith("model name") and ":" in line: + cpu_models.add(line.partition(":")[2].strip()) + numa_nodes = [] + node_root = Path("/sys/devices/system/node") + try: + numa_nodes = sorted( + path.name for path in node_root.glob("node[0-9]*") if path.is_dir() + ) + except OSError: + pass + governors = set() + for path in Path("/sys/devices/system/cpu").glob( + "cpu[0-9]*/cpufreq/scaling_governor" + ): + value = _read_optional_text(path) + if value is not None: + governors.add(value) + relevant_prefixes = ( + "OMP_", + "OMPI_", + "PMI_", + "PMIX_", + "UCX_", + "FI_", + "I_MPI_", + "MPICH_", + "SLURM_", + ) + relevant_names = {"PATH", "LD_LIBRARY_PATH"} + relevant_environment = { + name: value + for name, value in sorted(environment.items()) + if name in relevant_names or name.startswith(relevant_prefixes) + } + return { + "hostname": socket.gethostname(), + "platform": platform.platform(), + "kernel": platform.release(), + "machine": platform.machine(), + "python": platform.python_version(), + "logical_cpu_count": os.cpu_count(), + "allowed_cpus": affinity, + "cpu_models": sorted(cpu_models), + "numa_nodes": numa_nodes, + "memory": _read_optional_text(Path("/proc/meminfo")), + "scaling_governors": sorted(governors), + "turbo_disabled": _read_optional_text( + Path("/sys/devices/system/cpu/intel_pstate/no_turbo") + ), + "environment": relevant_environment, + } + + +def assemble_result_document( + *, + records: Iterable[Mapping[str, Any]], + provenance: Mapping[str, Any], + bootstrap_iterations: int, + bootstrap_seed: int, + min_pairs: int, + expected_cases: Iterable[Mapping[str, Any]] | None = None, +) -> dict[str, Any]: + materialized_records = [dict(record) for record in records] + quality = evaluate_acceptance( + materialized_records, + bootstrap_iterations=bootstrap_iterations, + bootstrap_seed=bootstrap_seed, + min_pairs=min_pairs, + expected_cases=expected_cases, + ) + collective_records = [record.get("collective_bytes") for record in materialized_records] + collective_complete = bool(collective_records) and all( + isinstance(record, Mapping) and record.get("status") == "complete" + for record in collective_records + ) + if collective_complete: + by_variant = { + variant: { + "global_sent_bytes": sum( + record["collective_bytes"]["global_sent_bytes"] + for record in materialized_records + if record["variant"] == variant + ), + "global_received_bytes": sum( + record["collective_bytes"]["global_received_bytes"] + for record in materialized_records + if record["variant"] == variant + ), + } + for variant in ("baseline", "candidate") + } + collective_bytes = { + "status": "complete", + "passed": True, + "measurement": "logical MPI endpoint payload bytes", + "variants": by_variant, + } + else: + collective_bytes = { + "status": "incomplete", + "passed": False, + "reason": ( + "no complete PMPI collective-byte records were supplied for " + "every rank of every run" + ), + } + stage_metrics = { + "status": "complete", + "passed": all( + isinstance(record.get("parhip"), Mapping) + and bool(record["parhip"].get("stage_events")) + for record in materialized_records + ), + } + if not stage_metrics["passed"]: + stage_metrics.update( + { + "status": "incomplete", + "reason": "one or more runs lack required stage timing records", + } + ) + topology_records = [ + record.get("topology_timing") for record in materialized_records + ] + topology_complete = bool(topology_records) and all( + isinstance(record, Mapping) and record.get("status") == "complete" + for record in topology_records + ) + if topology_complete: + topology_variants = { + variant: { + "global_calls": sum( + record["topology_timing"]["global_calls"] + for record in materialized_records + if record["variant"] == variant + ), + "global_rank_nanoseconds": sum( + record["topology_timing"]["global_rank_nanoseconds"] + for record in materialized_records + if record["variant"] == variant + ), + "total_run_max_rank_nanoseconds": sum( + record["topology_timing"]["max_rank_nanoseconds"] + for record in materialized_records + if record["variant"] == variant + ), + "max_rank_nanoseconds": max( + ( + record["topology_timing"]["max_rank_nanoseconds"] + for record in materialized_records + if record["variant"] == variant + ), + default=0, + ), + } + for variant in ("baseline", "candidate") + } + setup_topology = { + "status": "complete", + "passed": True, + "measurement": "PMPI distributed-graph construction wall time", + "variants": topology_variants, + } + else: + setup_topology = { + "status": "incomplete", + "passed": False, + "reason": ( + "one or more runs lack complete PMPI distributed-graph " + "topology timing" + ), + } + acceptance_complete = ( + stage_metrics["passed"] + and setup_topology["passed"] + and collective_bytes["passed"] + ) + return { + "schema_version": 1, + "provenance": dict(provenance), + "records": materialized_records, + "analysis": { + "quality_performance": quality, + "quality_performance_passed": bool( + quality["passed"] and stage_metrics["passed"] + ), + "stage_metrics": stage_metrics, + "setup_topology_timing": setup_topology, + "collective_bytes": collective_bytes, + "acceptance_complete": acceptance_complete, + "passed": bool( + quality["passed"] + and stage_metrics["passed"] + and acceptance_complete + ), + }, + } + + +def _utc_now() -> str: + return datetime.now(timezone.utc).isoformat() + + +def execute_harness( + config: Mapping[str, Any], + *, + prepare: bool, + executor: CommandExecutor = run_external_command, +) -> tuple[dict[str, Any], Path]: + validate_config(config) + expected_wrapper = Path(__file__).resolve().parents[1] / "run-limited" + configured_wrapper = Path(config["run_limited"]).resolve() + if configured_wrapper != expected_wrapper or not os.access( + configured_wrapper, os.X_OK + ): + raise ValueError( + f"run_limited must be the executable repository wrapper {expected_wrapper}" + ) + source_provenance_start = { + name: collect_git_provenance(Path(config["variants"][name]["source_directory"])) + for name in ("baseline", "candidate") + } + validate_pristine_baseline( + source_provenance_start["baseline"], config["pinned_upstream_revision"] + ) + + output_directory = Path(config["output_directory"]) + create_run_directory(output_directory) + event_path = output_directory / "events.jsonl" + started_at = _utc_now() + environment = benchmark_environment(config, os.environ) + config_snapshot = copy.deepcopy(dict(config)) + _write_json_atomic(output_directory / "configuration.json", config_snapshot) + _append_json_line( + event_path, + {"event": "started", "timestamp": started_at, "prepare": prepare}, + ) + + if prepare: + prepare_builds( + config, + output_directory=output_directory, + environment=environment, + executor=executor, + ) + + run_limited = Path(config["run_limited"]) + configured_mpiexec = Path(config["mpiexec"]["executable"]) + timeout_seconds = float(config["timeout_seconds"]) + build_provenance = { + name: collect_build_provenance( + config["variants"][name], + configured_mpiexec=configured_mpiexec, + run_limited=run_limited, + environment=environment, + timeout_seconds=timeout_seconds, + executor=executor, + ) + for name in ("baseline", "candidate") + } + validate_build_equivalence( + build_provenance["baseline"], + build_provenance["candidate"], + require_stage_timings=True, + ) + graphs, fixture_provenance = prepare_fixture_graphs( + config, + output_directory=output_directory, + environment=environment, + executor=executor, + ) + for record in fixture_provenance: + _append_json_line( + event_path, + {"event": "fixture-ready", "timestamp": _utc_now(), **record}, + ) + + fixtures = {fixture["name"]: fixture for fixture in config["fixtures"]} + cases = enumerate_cases(config) + records = [] + rank_runner = Path(__file__).with_name("rank_runner.py").resolve() + python_executable = Path(config.get("python_executable", sys.executable)) + collective_interposer = ( + Path(config["collective_bytes_interposer"]) + if "collective_bytes_interposer" in config + else None + ) + runs_root = output_directory / "runs" + for pair_index, case in enumerate(cases): + for execution_order, variant_name in enumerate( + alternating_variant_order(pair_index) + ): + run_directory = ( + runs_root / f"pair-{pair_index:06d}" / variant_name + ) + record = execute_one_run( + variant=variant_name, + case=case, + fixture=fixtures[case["fixture"]], + graph=graphs[case["fixture"]], + parhip=Path(config["variants"][variant_name]["executable"]), + run_directory=run_directory, + run_limited=run_limited, + mpiexec=configured_mpiexec, + numproc_flag=config["mpiexec"]["numproc_flag"], + mpi_preflags=config["mpiexec"]["preflags"], + python_executable=python_executable, + rank_runner=rank_runner, + environment=environment, + timeout_seconds=timeout_seconds, + executor=executor, + collective_bytes_interposer=collective_interposer, + ) + record["pair_index"] = pair_index + record["execution_order"] = execution_order + records.append(record) + _append_json_line( + event_path, + {"event": "run-complete", "timestamp": _utc_now(), **record}, + ) + + for fixture_record in fixture_provenance: + graph = Path(fixture_record["graph"]) + if _file_digest(graph) != fixture_record["graph_sha256"]: + raise ValueError(f"fixture graph changed during run: {graph}") + source_provenance_end = { + name: collect_git_provenance(Path(config["variants"][name]["source_directory"])) + for name in ("baseline", "candidate") + } + for name in ("baseline", "candidate"): + validate_unchanged_git_provenance( + source_provenance_start[name], + source_provenance_end[name], + variant=name, + ) + validate_pristine_baseline( + source_provenance_end["baseline"], config["pinned_upstream_revision"] + ) + finished_at = _utc_now() + provenance = { + "started_at": started_at, + "finished_at": finished_at, + "configuration_sha256": _file_digest( + output_directory / "configuration.json" + ), + "machine": machine_provenance(environment), + "mpi_launch": copy.deepcopy(config["mpiexec"]), + "concurrency": config.get("concurrency", 2), + "run_limited": { + "path": str(configured_wrapper), + "sha256": _file_digest(configured_wrapper), + }, + "git_start": source_provenance_start, + "git_end": source_provenance_end, + "builds": build_provenance, + "fixtures": fixture_provenance, + "collective_bytes_interposer": ( + { + "path": str(collective_interposer.resolve()), + "sha256": _file_digest(collective_interposer), + } + if collective_interposer is not None + else None + ), + } + bootstrap = config["bootstrap"] + document = assemble_result_document( + records=records, + provenance=provenance, + bootstrap_iterations=bootstrap["iterations"], + bootstrap_seed=bootstrap["seed"], + min_pairs=bootstrap["min_pairs"], + expected_cases=cases, + ) + result_path = output_directory / "results.json" + _write_json_atomic(result_path, document) + _append_json_line( + event_path, + { + "event": "finished", + "timestamp": finished_at, + "quality_performance_passed": document["analysis"][ + "quality_performance_passed" + ], + "acceptance_complete": document["analysis"]["acceptance_complete"], + "passed": document["analysis"]["passed"], + "results": str(result_path.resolve()), + }, + ) + return document, result_path + + +def main(arguments: Sequence[str] | None = None) -> int: + parser = argparse.ArgumentParser( + description="compare pristine-upstream and candidate ParHIP builds" + ) + parser.add_argument("--config", type=Path, required=True) + parser.add_argument( + "--prepare", + action="store_true", + help="run the configured Release configure/build commands first", + ) + options = parser.parse_args(arguments) + config: dict[str, Any] | None = None + try: + config = load_config(options.config) + document, result_path = execute_harness(config, prepare=options.prepare) + except (OSError, RuntimeError, ValueError) as error: + if config is not None: + output_directory = Path(config["output_directory"]) + if output_directory.is_dir(): + try: + _write_json_atomic( + output_directory / "failure.json", + { + "schema_version": 1, + "status": "failed", + "timestamp": _utc_now(), + "error": str(error), + }, + ) + except OSError: + pass + print(f"acceptance harness failed: {error}", file=sys.stderr) + return 2 + print(f"results: {result_path}") + if not document["analysis"]["acceptance_complete"]: + print( + "acceptance incomplete: collective bytes and isolated topology setup " + "timing are not yet available", + file=sys.stderr, + ) + return 2 + return 0 if document["analysis"]["passed"] else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/ci/performance/pmpi-collective-bytes.md b/ci/performance/pmpi-collective-bytes.md new file mode 100644 index 00000000..c9e164b4 --- /dev/null +++ b/ci/performance/pmpi-collective-bytes.md @@ -0,0 +1,95 @@ +# PMPI collective-byte and topology-setup measurement + +`pmpi_collective_bytes.cpp` is a standalone `LD_PRELOAD` interposer. It is not +registered in KaHIP's CMake build, so enabling measurement cannot change the +candidate or pristine-baseline binaries. + +## Measurement definition + +For every successful intercepted call, each rank records logical endpoint +payload bytes: + +- `sent_bytes` is the sum of nonnegative send counts times + `PMPI_Type_size_x(sendtype)`; +- `received_bytes` is the corresponding receive sum; +- self traffic is reported separately and remains included in sent/received; +- `MPI_PROC_NULL` Cartesian neighbors contribute zero; +- dense `MPI_IN_PLACE` sends are derived from the receive layout, while + neighborhood in-place use is marked invalid; +- derived datatypes use their MPI type size, not extent or buffer displacement. + +These are application-level logical bytes. They do not estimate eager/rendezvous +protocol headers, retransmission, shared-memory copies, NIC traffic, topology +effects, compression, or physical network-link bytes. Summing sent and received +counts both endpoints of a transfer by design. + +The same interposer measures successful `MPI_Dist_graph_create` and +`MPI_Dist_graph_create_adjacent` calls with `CLOCK_MONOTONIC`. Each rank records +the exact call count and elapsed nanoseconds for both constructors. This timing +covers the MPI constructor call only; KaHIP's graph analysis, sorting, and +payload exchanges remain in their normal stage and end-to-end measurements. + +The interposer covers `MPI_Alltoall`, `MPI_Alltoallv`, `MPI_Ialltoallv`, their +fixed/v neighborhood counterparts, MPI-4 large-count `_c` v variants, and +persistent neighborhood v variants. Optional MPI-4 symbols are resolved from +the next PMPI library at runtime, so an MPI-3 header can still build an +interposer capable of counting vendor-provided persistent collectives. + +Persistent init captures immutable layout metadata but charges no bytes. A +successful `MPI_Start` or `MPI_Startall` charges one generation; successful +`MPI_Request_free` removes the metadata. Nonblocking ordinary collectives are +charged once at successful initiation. A mutex protects counters and request +metadata for `MPI_THREAD_MULTIPLE`. + +Counts use checked unsigned 128-bit arithmetic. Negative counts, PMPI topology +or datatype-query failures, overflow, duplicate request identities, or live +persistent records at finalization set `complete=false`. The instrumentation +never replaces the underlying MPI return code. + +## Output and finalization + +Set `KAHIP_PMPI_BYTES_DIRECTORY` to an existing shared directory unique to one +MPI run. Immediately before `PMPI_Finalize`, each rank writes +`rank-.json` using an exclusive temporary file, `fsync`, and an atomic +hard link. No collective is called during finalization; the harness aggregates +rank files after `mpiexec` exits. Missing, duplicate, malformed, internally +inconsistent, or `complete=false` files make both byte and topology reporting +incomplete. + +## Standalone build and smoke test + +Use the same MPI compiler that built ParHIP: + +```shell +ci/run-limited mpicxx -std=c++23 -fPIC -shared \ + -Wall -Wextra -Wconversion -Werror \ + ci/performance/pmpi_collective_bytes.cpp -ldl \ + -o /tmp/kahip-pmpi-collective-bytes.so + +ci/run-limited mpicxx -std=c++23 -Wall -Wextra -Wconversion -Werror \ + -DKAHIP_PMPI_SMOKE_HAVE_PERSISTENT=1 \ + ci/performance/pmpi_collective_bytes_smoke.cpp \ + -o /tmp/kahip-pmpi-collective-bytes-smoke +``` + +Only set a smoke capability macro when that symbol is declared by the local +MPI headers. `KAHIP_PMPI_SMOKE_HAVE_LARGE_COUNTS=1` and +`KAHIP_PMPI_SMOKE_HAVE_PERSISTENT_C=1` exercise the corresponding `_c` paths on +an MPI-4 implementation. + +Run with a new output directory: + +```shell +ci/run-limited env \ + KAHIP_PMPI_BYTES_DIRECTORY=/tmp/kahip-pmpi-records \ + LD_PRELOAD=/tmp/kahip-pmpi-collective-bytes.so \ + mpiexec -n 2 /tmp/kahip-pmpi-collective-bytes-smoke +``` + +The harness passes the preload path to the per-rank `fork`/`execvpe` wrapper, +which prepends it to any existing `LD_PRELOAD` only in the ParHIP child. It is +not loaded into `systemd-run`, `mpiexec`, or the Python wrapper. The harness +creates a unique per-run output directory, requires exactly one complete record +per rank, matches its PID/hostname to the measured child, recomputes every rank +total from operation records, and records global send/receive, maximum-rank +endpoint bytes, and exact distributed-graph topology constructor timings. diff --git a/ci/performance/pmpi_collective_bytes.cpp b/ci/performance/pmpi_collective_bytes.cpp new file mode 100644 index 00000000..fbf9d8ce --- /dev/null +++ b/ci/performance/pmpi_collective_bytes.cpp @@ -0,0 +1,1265 @@ +// Standalone PMPI interposer for logical collective payload accounting and +// distributed-graph topology-construction timing. +// +// Build independently with an MPI compiler wrapper and inject with LD_PRELOAD. +// This file intentionally has no KaHIP or CMake dependency. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace { + +using wide_count = unsigned __int128; + +enum class operation : std::size_t { + alltoall, + alltoallv, + ialltoallv, + neighbor_alltoall, + neighbor_alltoallv, + ineighbor_alltoallv, + alltoallv_c, + ialltoallv_c, + neighbor_alltoallv_c, + ineighbor_alltoallv_c, + neighbor_alltoallv_persistent, + neighbor_alltoallv_persistent_c, + count, +}; + +constexpr auto operation_count = static_cast(operation::count); + +constexpr auto operation_names = std::array{ + "MPI_Alltoall", + "MPI_Alltoallv", + "MPI_Ialltoallv", + "MPI_Neighbor_alltoall", + "MPI_Neighbor_alltoallv", + "MPI_Ineighbor_alltoallv", + "MPI_Alltoallv_c", + "MPI_Ialltoallv_c", + "MPI_Neighbor_alltoallv_c", + "MPI_Ineighbor_alltoallv_c", + "MPI_Neighbor_alltoallv_init", + "MPI_Neighbor_alltoallv_init_c", +}; +static_assert(operation_names.size() == operation_count); + +enum class topology_operation : std::size_t { + distributed_graph, + distributed_graph_adjacent, + count, +}; + +constexpr auto topology_operation_count = + static_cast(topology_operation::count); + +constexpr auto topology_operation_names = std::array{ + "MPI_Dist_graph_create", + "MPI_Dist_graph_create_adjacent", +}; +static_assert(topology_operation_names.size() == topology_operation_count); + +struct traffic { + wide_count sent{}; + wide_count received{}; + wide_count self_sent{}; + wide_count self_received{}; +}; + +struct counter { + wide_count calls{}; + traffic bytes{}; +}; + +struct duration_counter { + wide_count calls{}; + wide_count elapsed_nanoseconds{}; +}; + +struct persistent_record { + operation kind{}; + traffic bytes{}; + bool valid{}; +}; + +struct accounting_state { + std::mutex mutex; + std::array counters{}; + std::array topology_counters{}; + std::unordered_map persistent; + bool complete{true}; + std::string first_error; +}; + +auto state() -> accounting_state& { + static accounting_state instance; + return instance; +} + +constexpr auto wide_max = ~wide_count{}; + +auto checked_add(wide_count left, wide_count right, wide_count& result) noexcept + -> bool { + if (right > wide_max - left) { + return false; + } + result = left + right; + return true; +} + +auto checked_multiply(wide_count left, + wide_count right, + wide_count& result) noexcept -> bool { + if (left != 0 && right > wide_max / left) { + return false; + } + result = left * right; + return true; +} + +void mark_error(std::string_view message) noexcept { + try { + auto& shared = state(); + auto lock = std::scoped_lock{shared.mutex}; + shared.complete = false; + if (shared.first_error.empty()) { + shared.first_error.assign(message); + } + } catch (...) { + // Instrumentation must never alter the MPI call's behavior. + } +} + +auto add_traffic(traffic& target, traffic const& value) noexcept -> bool { + return checked_add(target.sent, value.sent, target.sent) && + checked_add(target.received, value.received, target.received) && + checked_add(target.self_sent, value.self_sent, target.self_sent) && + checked_add(target.self_received, value.self_received, + target.self_received); +} + +void charge(operation kind, traffic const& bytes) noexcept { + try { + auto& shared = state(); + auto lock = std::scoped_lock{shared.mutex}; + auto& destination = shared.counters[static_cast(kind)]; + if (!checked_add(destination.calls, wide_count{1}, destination.calls) || + !add_traffic(destination.bytes, bytes)) { + shared.complete = false; + if (shared.first_error.empty()) { + shared.first_error = "collective byte counter overflow"; + } + } + } catch (...) { + mark_error("cannot update collective byte counters"); + } +} + +auto monotonic_nanoseconds() noexcept -> std::optional { + auto timestamp = timespec{}; + if (::clock_gettime(CLOCK_MONOTONIC, ×tamp) != 0 || + timestamp.tv_sec < 0 || timestamp.tv_nsec < 0 || + timestamp.tv_nsec >= 1'000'000'000L) { + mark_error("cannot read monotonic topology timer"); + return std::nullopt; + } + auto seconds = wide_count{}; + auto result = wide_count{}; + if (!checked_multiply(static_cast(timestamp.tv_sec), + wide_count{1'000'000'000}, seconds) || + !checked_add(seconds, static_cast(timestamp.tv_nsec), + result)) { + mark_error("topology timer representation overflow"); + return std::nullopt; + } + return result; +} + +void charge_topology(topology_operation kind, + std::optional started, + std::optional finished) noexcept { + if (!started || !finished) { + return; + } + if (*finished < *started) { + mark_error("monotonic topology timer moved backwards"); + return; + } + try { + auto& shared = state(); + auto lock = std::scoped_lock{shared.mutex}; + auto& destination = + shared.topology_counters[static_cast(kind)]; + auto const elapsed = *finished - *started; + if (!checked_add(destination.calls, wide_count{1}, destination.calls) || + !checked_add(destination.elapsed_nanoseconds, elapsed, + destination.elapsed_nanoseconds)) { + shared.complete = false; + if (shared.first_error.empty()) { + shared.first_error = "topology timing counter overflow"; + } + } + } catch (...) { + mark_error("cannot update topology timing counters"); + } +} + +auto datatype_size(MPI_Datatype datatype) noexcept + -> std::optional { + MPI_Count size{}; + if (PMPI_Type_size_x(datatype, &size) != MPI_SUCCESS || size < 0) { + mark_error("PMPI_Type_size_x failed"); + return std::nullopt; + } + return static_cast(size); +} + +template +auto nonnegative_count(Count value) noexcept -> std::optional { + if (value < 0) { + mark_error("negative collective count"); + return std::nullopt; + } + return static_cast(value); +} + +template +auto add_count_bytes(traffic& bytes, + wide_count traffic::* field, + Count count, + wide_count type_size) noexcept -> bool { + auto const normalized = nonnegative_count(count); + if (!normalized) { + return false; + } + wide_count product{}; + if (!checked_multiply(*normalized, type_size, product) || + !checked_add(bytes.*field, product, bytes.*field)) { + mark_error("collective payload byte count overflow"); + return false; + } + return true; +} + +template +auto dense_v_traffic(void const* send_buffer, + Count const* send_counts, + MPI_Datatype send_type, + Count const* receive_counts, + MPI_Datatype receive_type, + MPI_Comm communicator) noexcept -> std::optional { + int size{}; + int rank{}; + if (PMPI_Comm_size(communicator, &size) != MPI_SUCCESS || size < 0 || + PMPI_Comm_rank(communicator, &rank) != MPI_SUCCESS || rank < 0 || + rank >= size) { + mark_error("cannot query dense communicator shape"); + return std::nullopt; + } + if (size != 0 && receive_counts == nullptr) { + mark_error("null dense receive-count array"); + return std::nullopt; + } + auto const receive_size = datatype_size(receive_type); + if (!receive_size) { + return std::nullopt; + } + traffic bytes; + for (int peer = 0; peer < size; ++peer) { + if (!add_count_bytes(bytes, &traffic::received, receive_counts[peer], + *receive_size)) { + return std::nullopt; + } + if (peer == rank && !add_count_bytes(bytes, &traffic::self_received, + receive_counts[peer], *receive_size)) { + return std::nullopt; + } + } + if (send_buffer == MPI_IN_PLACE) { + bytes.sent = bytes.received; + bytes.self_sent = bytes.self_received; + return bytes; + } + if (size != 0 && send_counts == nullptr) { + mark_error("null dense send-count array"); + return std::nullopt; + } + auto const send_size = datatype_size(send_type); + if (!send_size) { + return std::nullopt; + } + for (int peer = 0; peer < size; ++peer) { + if (!add_count_bytes(bytes, &traffic::sent, send_counts[peer], + *send_size)) { + return std::nullopt; + } + if (peer == rank && !add_count_bytes(bytes, &traffic::self_sent, + send_counts[peer], *send_size)) { + return std::nullopt; + } + } + return bytes; +} + +auto dense_fixed_traffic(void const* send_buffer, + int send_count, + MPI_Datatype send_type, + int receive_count, + MPI_Datatype receive_type, + MPI_Comm communicator) noexcept + -> std::optional { + int size{}; + int rank{}; + if (PMPI_Comm_size(communicator, &size) != MPI_SUCCESS || size < 0 || + PMPI_Comm_rank(communicator, &rank) != MPI_SUCCESS || rank < 0 || + rank >= size) { + mark_error("cannot query dense communicator shape"); + return std::nullopt; + } + auto const receive_size = datatype_size(receive_type); + auto const normalized_receive = nonnegative_count(receive_count); + if (!receive_size || !normalized_receive) { + return std::nullopt; + } + wide_count one_receive{}; + wide_count all_receives{}; + if (!checked_multiply(*normalized_receive, *receive_size, one_receive) || + !checked_multiply(one_receive, static_cast(size), + all_receives)) { + mark_error("dense fixed-count payload overflow"); + return std::nullopt; + } + traffic bytes{.received = all_receives, .self_received = one_receive}; + if (send_buffer == MPI_IN_PLACE) { + bytes.sent = bytes.received; + bytes.self_sent = bytes.self_received; + return bytes; + } + auto const send_size = datatype_size(send_type); + auto const normalized_send = nonnegative_count(send_count); + if (!send_size || !normalized_send) { + return std::nullopt; + } + wide_count one_send{}; + if (!checked_multiply(*normalized_send, *send_size, one_send) || + !checked_multiply(one_send, static_cast(size), bytes.sent)) { + mark_error("dense fixed-count payload overflow"); + return std::nullopt; + } + bytes.self_sent = one_send; + return bytes; +} + +struct neighborhood { + int rank{}; + std::vector sources; + std::vector destinations; +}; + +auto communicator_neighborhood(MPI_Comm communicator) noexcept + -> std::optional { + neighborhood result; + if (PMPI_Comm_rank(communicator, &result.rank) != MPI_SUCCESS) { + mark_error("cannot query neighborhood rank"); + return std::nullopt; + } + int topology{}; + if (PMPI_Topo_test(communicator, &topology) != MPI_SUCCESS) { + mark_error("PMPI_Topo_test failed"); + return std::nullopt; + } + if (topology == MPI_DIST_GRAPH) { + int indegree{}; + int outdegree{}; + int weighted{}; + if (PMPI_Dist_graph_neighbors_count(communicator, &indegree, &outdegree, + &weighted) != MPI_SUCCESS || + indegree < 0 || outdegree < 0) { + mark_error("cannot query distributed-graph neighborhood size"); + return std::nullopt; + } + result.sources.resize(static_cast(indegree)); + result.destinations.resize(static_cast(outdegree)); + if (PMPI_Dist_graph_neighbors(communicator, indegree, result.sources.data(), + MPI_UNWEIGHTED, outdegree, + result.destinations.data(), + MPI_UNWEIGHTED) != MPI_SUCCESS) { + mark_error("cannot query distributed-graph neighbors"); + return std::nullopt; + } + return result; + } + if (topology == MPI_GRAPH) { + int degree{}; + if (PMPI_Graph_neighbors_count(communicator, result.rank, °ree) != + MPI_SUCCESS || + degree < 0) { + mark_error("cannot query graph neighborhood size"); + return std::nullopt; + } + result.sources.resize(static_cast(degree)); + if (PMPI_Graph_neighbors(communicator, result.rank, degree, + result.sources.data()) != MPI_SUCCESS) { + mark_error("cannot query graph neighbors"); + return std::nullopt; + } + result.destinations = result.sources; + return result; + } + if (topology == MPI_CART) { + int dimensions{}; + if (PMPI_Cartdim_get(communicator, &dimensions) != MPI_SUCCESS || + dimensions < 0) { + mark_error("cannot query Cartesian neighborhood size"); + return std::nullopt; + } + result.sources.reserve(static_cast(2 * dimensions)); + result.destinations.reserve(static_cast(2 * dimensions)); + for (int dimension = 0; dimension < dimensions; ++dimension) { + int negative{}; + int positive{}; + if (PMPI_Cart_shift(communicator, dimension, 1, &negative, &positive) != + MPI_SUCCESS) { + mark_error("cannot query Cartesian neighbors"); + return std::nullopt; + } + result.sources.push_back(negative); + result.sources.push_back(positive); + result.destinations.push_back(negative); + result.destinations.push_back(positive); + } + return result; + } + mark_error("neighborhood collective used on communicator without topology"); + return std::nullopt; +} + +template +auto neighbor_v_traffic(void const* send_buffer, + Count const* send_counts, + MPI_Datatype send_type, + Count const* receive_counts, + MPI_Datatype receive_type, + MPI_Comm communicator) noexcept + -> std::optional { + if (send_buffer == MPI_IN_PLACE) { + mark_error("MPI_IN_PLACE is invalid for neighborhood collectives"); + return std::nullopt; + } + auto const neighbors = communicator_neighborhood(communicator); + auto const send_size = datatype_size(send_type); + auto const receive_size = datatype_size(receive_type); + if (!neighbors || !send_size || !receive_size) { + return std::nullopt; + } + if ((!neighbors->destinations.empty() && send_counts == nullptr) || + (!neighbors->sources.empty() && receive_counts == nullptr)) { + mark_error("null neighborhood count array"); + return std::nullopt; + } + traffic bytes; + for (std::size_t index = 0; index < neighbors->destinations.size(); ++index) { + auto const peer = neighbors->destinations[index]; + if (peer == MPI_PROC_NULL) { + continue; + } + if (!add_count_bytes(bytes, &traffic::sent, send_counts[index], + *send_size)) { + return std::nullopt; + } + if (peer == neighbors->rank && + !add_count_bytes(bytes, &traffic::self_sent, send_counts[index], + *send_size)) { + return std::nullopt; + } + } + for (std::size_t index = 0; index < neighbors->sources.size(); ++index) { + auto const peer = neighbors->sources[index]; + if (peer == MPI_PROC_NULL) { + continue; + } + if (!add_count_bytes(bytes, &traffic::received, receive_counts[index], + *receive_size)) { + return std::nullopt; + } + if (peer == neighbors->rank && + !add_count_bytes(bytes, &traffic::self_received, receive_counts[index], + *receive_size)) { + return std::nullopt; + } + } + return bytes; +} + +auto neighbor_fixed_traffic(void const* send_buffer, + int send_count, + MPI_Datatype send_type, + int receive_count, + MPI_Datatype receive_type, + MPI_Comm communicator) noexcept + -> std::optional { + if (send_buffer == MPI_IN_PLACE) { + mark_error("MPI_IN_PLACE is invalid for neighborhood collectives"); + return std::nullopt; + } + auto const neighbors = communicator_neighborhood(communicator); + auto const send_size = datatype_size(send_type); + auto const receive_size = datatype_size(receive_type); + auto const normalized_send = nonnegative_count(send_count); + auto const normalized_receive = nonnegative_count(receive_count); + if (!neighbors || !send_size || !receive_size || !normalized_send || + !normalized_receive) { + return std::nullopt; + } + wide_count send_bytes{}; + wide_count receive_bytes{}; + if (!checked_multiply(*normalized_send, *send_size, send_bytes) || + !checked_multiply(*normalized_receive, *receive_size, receive_bytes)) { + mark_error("neighborhood fixed-count payload overflow"); + return std::nullopt; + } + traffic bytes; + for (auto const peer : neighbors->destinations) { + if (peer == MPI_PROC_NULL) { + continue; + } + if (!checked_add(bytes.sent, send_bytes, bytes.sent)) { + mark_error("neighborhood fixed-count payload overflow"); + return std::nullopt; + } + if (peer == neighbors->rank && + !checked_add(bytes.self_sent, send_bytes, bytes.self_sent)) { + mark_error("neighborhood fixed-count payload overflow"); + return std::nullopt; + } + } + for (auto const peer : neighbors->sources) { + if (peer == MPI_PROC_NULL) { + continue; + } + if (!checked_add(bytes.received, receive_bytes, bytes.received)) { + mark_error("neighborhood fixed-count payload overflow"); + return std::nullopt; + } + if (peer == neighbors->rank && + !checked_add(bytes.self_received, receive_bytes, bytes.self_received)) { + mark_error("neighborhood fixed-count payload overflow"); + return std::nullopt; + } + } + return bytes; +} + +auto request_key(MPI_Request request) noexcept -> MPI_Fint { + return PMPI_Request_c2f(request); +} + +void remember_persistent(MPI_Request request, + operation kind, + std::optional const& bytes) noexcept { + if (request == MPI_REQUEST_NULL) { + mark_error("persistent collective init returned MPI_REQUEST_NULL"); + return; + } + try { + auto& shared = state(); + auto lock = std::scoped_lock{shared.mutex}; + auto const key = request_key(request); + auto const [_, inserted] = shared.persistent.emplace( + key, persistent_record{.kind = kind, + .bytes = bytes.value_or(traffic{}), + .valid = bytes.has_value()}); + if (!inserted) { + shared.complete = false; + if (shared.first_error.empty()) { + shared.first_error = "duplicate persistent MPI request identity"; + } + } + } catch (...) { + mark_error("cannot retain persistent collective metadata"); + } +} + +template +auto resolve_optional_pmpi(char const* name) noexcept -> Function { + auto* symbol = ::dlsym(RTLD_NEXT, name); + Function function{}; + static_assert(sizeof(function) == sizeof(symbol)); + std::memcpy(&function, &symbol, sizeof(function)); + if (function == nullptr) { + mark_error(std::string{"cannot resolve "} + name); + } + return function; +} + +void charge_persistent(MPI_Request request) noexcept { + try { + auto& shared = state(); + auto lock = std::scoped_lock{shared.mutex}; + auto const found = shared.persistent.find(request_key(request)); + if (found == shared.persistent.end()) { + return; // May be a persistent point-to-point request. + } + if (!found->second.valid) { + shared.complete = false; + return; + } + auto& destination = + shared.counters[static_cast(found->second.kind)]; + if (!checked_add(destination.calls, wide_count{1}, destination.calls) || + !add_traffic(destination.bytes, found->second.bytes)) { + shared.complete = false; + if (shared.first_error.empty()) { + shared.first_error = "persistent collective byte counter overflow"; + } + } + } catch (...) { + mark_error("cannot charge persistent collective metadata"); + } +} + +void forget_request(MPI_Request request) noexcept { + try { + auto& shared = state(); + auto lock = std::scoped_lock{shared.mutex}; + shared.persistent.erase(request_key(request)); + } catch (...) { + mark_error("cannot erase persistent collective metadata"); + } +} + +auto wide_string(wide_count value) -> std::string { + if (value == 0) { + return "0"; + } + std::string result; + while (value != 0) { + result.push_back(static_cast('0' + value % 10)); + value /= 10; + } + std::reverse(result.begin(), result.end()); + return result; +} + +auto json_escape(std::string_view value) -> std::string { + std::ostringstream output; + for (auto const character : value) { + switch (character) { + case '\\': + output << "\\\\"; + break; + case '"': + output << "\\\""; + break; + case '\n': + output << "\\n"; + break; + case '\r': + output << "\\r"; + break; + case '\t': + output << "\\t"; + break; + default: + if (static_cast(character) < 0x20) { + output << "?"; + } else { + output << character; + } + } + } + return output.str(); +} + +struct state_snapshot { + std::array counters{}; + std::array topology_counters{}; + bool complete{}; + std::string first_error; + std::size_t live_persistent_requests{}; +}; + +auto snapshot() -> state_snapshot { + auto& shared = state(); + auto lock = std::scoped_lock{shared.mutex}; + return {.counters = shared.counters, + .topology_counters = shared.topology_counters, + .complete = shared.complete, + .first_error = shared.first_error, + .live_persistent_requests = shared.persistent.size()}; +} + +auto render_json(int rank, + std::string_view hostname, + state_snapshot const& data) -> std::string { + traffic totals; + wide_count calls{}; + for (auto const& value : data.counters) { + checked_add(calls, value.calls, calls); + add_traffic(totals, value.bytes); + } + auto topology_totals = duration_counter{}; + for (auto const& value : data.topology_counters) { + checked_add(topology_totals.calls, value.calls, topology_totals.calls); + checked_add(topology_totals.elapsed_nanoseconds, + value.elapsed_nanoseconds, + topology_totals.elapsed_nanoseconds); + } + std::ostringstream output; + output << "{\"schema_version\":1,\"rank\":" << rank + << ",\"pid\":" << static_cast(::getpid()) + << ",\"hostname\":\"" << json_escape(hostname) << "\"" + << ",\"complete\":" << (data.complete ? "true" : "false") + << ",\"error\":"; + if (data.first_error.empty()) { + output << "null"; + } else { + output << "\"" << json_escape(data.first_error) << "\""; + } + output << ",\"live_persistent_requests\":" << data.live_persistent_requests + << ",\"operations\": ["; + for (std::size_t index = 0; index < data.counters.size(); ++index) { + if (index != 0) { + output << ','; + } + auto const& value = data.counters[index]; + output << "{\"name\":\"" << operation_names[index] << "\"" + << ",\"calls\":" << wide_string(value.calls) + << ",\"sent_bytes\":" << wide_string(value.bytes.sent) + << ",\"received_bytes\":" << wide_string(value.bytes.received) + << ",\"self_sent_bytes\":" << wide_string(value.bytes.self_sent) + << ",\"self_received_bytes\":" + << wide_string(value.bytes.self_received) << '}'; + } + output << "],\"topology_setup\":{\"operations\":["; + for (std::size_t index = 0; index < data.topology_counters.size(); ++index) { + if (index != 0) { + output << ','; + } + auto const& value = data.topology_counters[index]; + output << "{\"name\":\"" << topology_operation_names[index] << "\"" + << ",\"calls\":" << wide_string(value.calls) + << ",\"elapsed_nanoseconds\":" + << wide_string(value.elapsed_nanoseconds) << '}'; + } + output << "],\"totals\":{\"calls\":" + << wide_string(topology_totals.calls) + << ",\"elapsed_nanoseconds\":" + << wide_string(topology_totals.elapsed_nanoseconds) + << "}},\"totals\":{\"calls\":" << wide_string(calls) + << ",\"sent_bytes\":" << wide_string(totals.sent) + << ",\"received_bytes\":" << wide_string(totals.received) + << ",\"self_sent_bytes\":" << wide_string(totals.self_sent) + << ",\"self_received_bytes\":" << wide_string(totals.self_received) + << "}}\n"; + return output.str(); +} + +auto write_all(int descriptor, std::string_view value) noexcept -> bool { + std::size_t offset{}; + while (offset < value.size()) { + auto const written = + ::write(descriptor, value.data() + offset, value.size() - offset); + if (written < 0 && errno == EINTR) { + continue; + } + if (written <= 0) { + return false; + } + offset += static_cast(written); + } + return true; +} + +void error_marker(int rank, std::string_view message) noexcept { + auto const record = "KAHIP_PMPI_BYTES_ERROR rank=" + std::to_string(rank) + + " message=" + std::string(message) + "\n"; + write_all(STDERR_FILENO, record); +} + +auto write_rank_record(int rank, std::string const& record) noexcept -> bool { + auto const* raw_directory = std::getenv("KAHIP_PMPI_BYTES_DIRECTORY"); + if (raw_directory == nullptr || *raw_directory == '\0') { + error_marker(rank, "KAHIP_PMPI_BYTES_DIRECTORY is unset"); + return false; + } + auto const directory = std::string{raw_directory}; + auto const final_path = directory + "/rank-" + std::to_string(rank) + ".json"; + auto const temporary_path = directory + "/.rank-" + std::to_string(rank) + + "." + std::to_string(::getpid()) + ".tmp"; + auto const descriptor = + ::open(temporary_path.c_str(), O_WRONLY | O_CREAT | O_EXCL, 0600); + if (descriptor < 0) { + error_marker(rank, std::strerror(errno)); + return false; + } + auto success = write_all(descriptor, record); + if (success && ::fsync(descriptor) != 0) { + success = false; + } + if (::close(descriptor) != 0) { + success = false; + } + if (success && ::link(temporary_path.c_str(), final_path.c_str()) != 0) { + success = false; + } + auto const saved_error = errno; + ::unlink(temporary_path.c_str()); + if (!success) { + error_marker(rank, std::strerror(saved_error)); + } + return success; +} + +void emit_rank_record() noexcept { + int rank{-1}; + if (PMPI_Comm_rank(MPI_COMM_WORLD, &rank) != MPI_SUCCESS) { + error_marker(rank, "PMPI_Comm_rank failed during accounting output"); + return; + } + std::array hostname{}; + if (::gethostname(hostname.data(), hostname.size()) != 0) { + hostname.front() = '?'; + hostname[1] = '\0'; + } else { + hostname.back() = '\0'; + } + try { + auto data = snapshot(); + if (data.live_persistent_requests != 0) { + data.complete = false; + if (data.first_error.empty()) { + data.first_error = + "persistent collective requests still live at finalize"; + } + } + write_rank_record(rank, render_json(rank, hostname.data(), data)); + } catch (...) { + error_marker(rank, "cannot render collective accounting record"); + } +} + +} // namespace + +extern "C" int MPI_Dist_graph_create(MPI_Comm old_communicator, + int source_count, + int const sources[], + int const degrees[], + int const destinations[], + int const weights[], + MPI_Info info, + int reorder, + MPI_Comm* graph_communicator) { + auto const started = monotonic_nanoseconds(); + auto const result = PMPI_Dist_graph_create( + old_communicator, source_count, sources, degrees, destinations, weights, + info, reorder, graph_communicator); + auto const finished = monotonic_nanoseconds(); + if (result == MPI_SUCCESS) { + charge_topology(topology_operation::distributed_graph, started, finished); + } + return result; +} + +extern "C" int MPI_Dist_graph_create_adjacent( + MPI_Comm old_communicator, + int indegree, + int const sources[], + int const source_weights[], + int outdegree, + int const destinations[], + int const destination_weights[], + MPI_Info info, + int reorder, + MPI_Comm* graph_communicator) { + auto const started = monotonic_nanoseconds(); + auto const result = PMPI_Dist_graph_create_adjacent( + old_communicator, indegree, sources, source_weights, outdegree, + destinations, destination_weights, info, reorder, graph_communicator); + auto const finished = monotonic_nanoseconds(); + if (result == MPI_SUCCESS) { + charge_topology(topology_operation::distributed_graph_adjacent, started, + finished); + } + return result; +} + +extern "C" int MPI_Alltoall(void const* send_buffer, + int send_count, + MPI_Datatype send_type, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_type, + MPI_Comm communicator) { + auto const bytes = + dense_fixed_traffic(send_buffer, send_count, send_type, receive_count, + receive_type, communicator); + auto const result = + PMPI_Alltoall(send_buffer, send_count, send_type, receive_buffer, + receive_count, receive_type, communicator); + if (result == MPI_SUCCESS && bytes) { + charge(operation::alltoall, *bytes); + } + return result; +} + +extern "C" int MPI_Alltoallv(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_type, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_type, + MPI_Comm communicator) { + auto const bytes = + dense_v_traffic(send_buffer, send_counts, send_type, receive_counts, + receive_type, communicator); + auto const result = PMPI_Alltoallv( + send_buffer, send_counts, send_displacements, send_type, receive_buffer, + receive_counts, receive_displacements, receive_type, communicator); + if (result == MPI_SUCCESS && bytes) { + charge(operation::alltoallv, *bytes); + } + return result; +} + +extern "C" int MPI_Ialltoallv(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_type, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_type, + MPI_Comm communicator, + MPI_Request* request) { + auto const bytes = + dense_v_traffic(send_buffer, send_counts, send_type, receive_counts, + receive_type, communicator); + auto const result = + PMPI_Ialltoallv(send_buffer, send_counts, send_displacements, send_type, + receive_buffer, receive_counts, receive_displacements, + receive_type, communicator, request); + if (result == MPI_SUCCESS && bytes) { + charge(operation::ialltoallv, *bytes); + } + return result; +} + +extern "C" int MPI_Neighbor_alltoall(void const* send_buffer, + int send_count, + MPI_Datatype send_type, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_type, + MPI_Comm communicator) { + auto const bytes = + neighbor_fixed_traffic(send_buffer, send_count, send_type, receive_count, + receive_type, communicator); + auto const result = + PMPI_Neighbor_alltoall(send_buffer, send_count, send_type, receive_buffer, + receive_count, receive_type, communicator); + if (result == MPI_SUCCESS && bytes) { + charge(operation::neighbor_alltoall, *bytes); + } + return result; +} + +extern "C" int MPI_Neighbor_alltoallv(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_type, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_type, + MPI_Comm communicator) { + auto const bytes = + neighbor_v_traffic(send_buffer, send_counts, send_type, receive_counts, + receive_type, communicator); + auto const result = PMPI_Neighbor_alltoallv( + send_buffer, send_counts, send_displacements, send_type, receive_buffer, + receive_counts, receive_displacements, receive_type, communicator); + if (result == MPI_SUCCESS && bytes) { + charge(operation::neighbor_alltoallv, *bytes); + } + return result; +} + +extern "C" int MPI_Ineighbor_alltoallv(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_type, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_type, + MPI_Comm communicator, + MPI_Request* request) { + auto const bytes = + neighbor_v_traffic(send_buffer, send_counts, send_type, receive_counts, + receive_type, communicator); + auto const result = PMPI_Ineighbor_alltoallv( + send_buffer, send_counts, send_displacements, send_type, receive_buffer, + receive_counts, receive_displacements, receive_type, communicator, + request); + if (result == MPI_SUCCESS && bytes) { + charge(operation::ineighbor_alltoallv, *bytes); + } + return result; +} + +extern "C" int MPI_Alltoallv_c(void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_type, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_type, + MPI_Comm communicator) { + using pmpi_function = int (*)( + void const*, MPI_Count const[], MPI_Aint const[], MPI_Datatype, void*, + MPI_Count const[], MPI_Aint const[], MPI_Datatype, MPI_Comm); + static auto const pmpi = + resolve_optional_pmpi("PMPI_Alltoallv_c"); + if (pmpi == nullptr) { + return MPI_ERR_OTHER; + } + auto const bytes = + dense_v_traffic(send_buffer, send_counts, send_type, receive_counts, + receive_type, communicator); + auto const result = pmpi(send_buffer, send_counts, send_displacements, + send_type, receive_buffer, receive_counts, + receive_displacements, receive_type, communicator); + if (result == MPI_SUCCESS && bytes) { + charge(operation::alltoallv_c, *bytes); + } + return result; +} + +extern "C" int MPI_Ialltoallv_c(void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_type, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_type, + MPI_Comm communicator, + MPI_Request* request) { + using pmpi_function = + int (*)(void const*, MPI_Count const[], MPI_Aint const[], MPI_Datatype, + void*, MPI_Count const[], MPI_Aint const[], MPI_Datatype, + MPI_Comm, MPI_Request*); + static auto const pmpi = + resolve_optional_pmpi("PMPI_Ialltoallv_c"); + if (pmpi == nullptr) { + return MPI_ERR_OTHER; + } + auto const bytes = + dense_v_traffic(send_buffer, send_counts, send_type, receive_counts, + receive_type, communicator); + auto const result = + pmpi(send_buffer, send_counts, send_displacements, send_type, + receive_buffer, receive_counts, receive_displacements, receive_type, + communicator, request); + if (result == MPI_SUCCESS && bytes) { + charge(operation::ialltoallv_c, *bytes); + } + return result; +} + +extern "C" int MPI_Neighbor_alltoallv_c(void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_type, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_type, + MPI_Comm communicator) { + using pmpi_function = int (*)( + void const*, MPI_Count const[], MPI_Aint const[], MPI_Datatype, void*, + MPI_Count const[], MPI_Aint const[], MPI_Datatype, MPI_Comm); + static auto const pmpi = + resolve_optional_pmpi("PMPI_Neighbor_alltoallv_c"); + if (pmpi == nullptr) { + return MPI_ERR_OTHER; + } + auto const bytes = + neighbor_v_traffic(send_buffer, send_counts, send_type, receive_counts, + receive_type, communicator); + auto const result = pmpi(send_buffer, send_counts, send_displacements, + send_type, receive_buffer, receive_counts, + receive_displacements, receive_type, communicator); + if (result == MPI_SUCCESS && bytes) { + charge(operation::neighbor_alltoallv_c, *bytes); + } + return result; +} + +extern "C" int MPI_Ineighbor_alltoallv_c(void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_type, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_type, + MPI_Comm communicator, + MPI_Request* request) { + using pmpi_function = + int (*)(void const*, MPI_Count const[], MPI_Aint const[], MPI_Datatype, + void*, MPI_Count const[], MPI_Aint const[], MPI_Datatype, + MPI_Comm, MPI_Request*); + static auto const pmpi = + resolve_optional_pmpi("PMPI_Ineighbor_alltoallv_c"); + if (pmpi == nullptr) { + return MPI_ERR_OTHER; + } + auto const bytes = + neighbor_v_traffic(send_buffer, send_counts, send_type, receive_counts, + receive_type, communicator); + auto const result = + pmpi(send_buffer, send_counts, send_displacements, send_type, + receive_buffer, receive_counts, receive_displacements, receive_type, + communicator, request); + if (result == MPI_SUCCESS && bytes) { + charge(operation::ineighbor_alltoallv_c, *bytes); + } + return result; +} + +extern "C" int MPI_Neighbor_alltoallv_init(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_type, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_type, + MPI_Comm communicator, + MPI_Info info, + MPI_Request* request) { + using pmpi_function = int (*)(void const*, int const[], int const[], + MPI_Datatype, void*, int const[], int const[], + MPI_Datatype, MPI_Comm, MPI_Info, MPI_Request*); + static auto const pmpi = + resolve_optional_pmpi("PMPI_Neighbor_alltoallv_init"); + if (pmpi == nullptr) { + return MPI_ERR_OTHER; + } + auto const bytes = + neighbor_v_traffic(send_buffer, send_counts, send_type, receive_counts, + receive_type, communicator); + auto const result = + pmpi(send_buffer, send_counts, send_displacements, send_type, + receive_buffer, receive_counts, receive_displacements, receive_type, + communicator, info, request); + if (result == MPI_SUCCESS && request != nullptr) { + remember_persistent(*request, operation::neighbor_alltoallv_persistent, + bytes); + } + return result; +} + +extern "C" int MPI_Neighbor_alltoallv_init_c( + void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_type, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_type, + MPI_Comm communicator, + MPI_Info info, + MPI_Request* request) { + using pmpi_function = + int (*)(void const*, MPI_Count const[], MPI_Aint const[], MPI_Datatype, + void*, MPI_Count const[], MPI_Aint const[], MPI_Datatype, + MPI_Comm, MPI_Info, MPI_Request*); + static auto const pmpi = + resolve_optional_pmpi("PMPI_Neighbor_alltoallv_init_c"); + if (pmpi == nullptr) { + return MPI_ERR_OTHER; + } + auto const bytes = + neighbor_v_traffic(send_buffer, send_counts, send_type, receive_counts, + receive_type, communicator); + auto const result = + pmpi(send_buffer, send_counts, send_displacements, send_type, + receive_buffer, receive_counts, receive_displacements, receive_type, + communicator, info, request); + if (result == MPI_SUCCESS && request != nullptr) { + remember_persistent(*request, operation::neighbor_alltoallv_persistent_c, + bytes); + } + return result; +} + +extern "C" int MPI_Start(MPI_Request* request) { + auto const before = request == nullptr ? MPI_REQUEST_NULL : *request; + auto const result = PMPI_Start(request); + if (result == MPI_SUCCESS && before != MPI_REQUEST_NULL) { + charge_persistent(before); + } + return result; +} + +extern "C" int MPI_Startall(int count, MPI_Request requests[]) { + std::vector before; + try { + if (count > 0 && requests != nullptr) { + before.assign(requests, requests + count); + } + } catch (...) { + mark_error("cannot snapshot MPI_Startall requests"); + } + auto const result = PMPI_Startall(count, requests); + if (result == MPI_SUCCESS) { + for (auto const request : before) { + if (request != MPI_REQUEST_NULL) { + charge_persistent(request); + } + } + } + return result; +} + +extern "C" int MPI_Request_free(MPI_Request* request) { + auto const before = request == nullptr ? MPI_REQUEST_NULL : *request; + auto const result = PMPI_Request_free(request); + if (result == MPI_SUCCESS && before != MPI_REQUEST_NULL) { + forget_request(before); + } + return result; +} + +extern "C" int MPI_Finalize() { + emit_rank_record(); + return PMPI_Finalize(); +} diff --git a/ci/performance/pmpi_collective_bytes_smoke.cpp b/ci/performance/pmpi_collective_bytes_smoke.cpp new file mode 100644 index 00000000..0aa0b4f9 --- /dev/null +++ b/ci/performance/pmpi_collective_bytes_smoke.cpp @@ -0,0 +1,167 @@ +#include + +#include +#include +#include +#include + +#ifndef KAHIP_PMPI_SMOKE_HAVE_LARGE_COUNTS +#define KAHIP_PMPI_SMOKE_HAVE_LARGE_COUNTS (MPI_VERSION >= 4) +#endif + +#ifndef KAHIP_PMPI_SMOKE_HAVE_PERSISTENT +#define KAHIP_PMPI_SMOKE_HAVE_PERSISTENT (MPI_VERSION >= 4) +#endif + +#ifndef KAHIP_PMPI_SMOKE_HAVE_PERSISTENT_C +#define KAHIP_PMPI_SMOKE_HAVE_PERSISTENT_C (MPI_VERSION >= 4) +#endif + +namespace { + +void require(int result, char const* operation) { + if (result != MPI_SUCCESS) { + std::cerr << operation << " failed with MPI status " << result << '\n'; + MPI_Abort(MPI_COMM_WORLD, result); + } +} + +} // namespace + +int main(int argc, char* argv[]) { + int supplied{}; + require(MPI_Init_thread(&argc, &argv, MPI_THREAD_MULTIPLE, &supplied), + "MPI_Init_thread"); + int size{}; + int rank{}; + require(MPI_Comm_size(MPI_COMM_WORLD, &size), "MPI_Comm_size"); + require(MPI_Comm_rank(MPI_COMM_WORLD, &rank), "MPI_Comm_rank"); + if (size <= 0) { + MPI_Abort(MPI_COMM_WORLD, 2); + } + + auto counts = std::vector(static_cast(size), 1); + auto displacements = std::vector(static_cast(size)); + for (int index = 0; index < size; ++index) { + displacements[static_cast(index)] = index; + } + auto sends = std::vector(static_cast(size), rank); + auto receives = std::vector(static_cast(size)); + require(MPI_Alltoall(sends.data(), 1, MPI_INT, receives.data(), 1, MPI_INT, + MPI_COMM_WORLD), + "MPI_Alltoall"); + require(MPI_Alltoallv(sends.data(), counts.data(), displacements.data(), + MPI_INT, receives.data(), counts.data(), + displacements.data(), MPI_INT, MPI_COMM_WORLD), + "MPI_Alltoallv"); + MPI_Request request{MPI_REQUEST_NULL}; + require( + MPI_Ialltoallv(sends.data(), counts.data(), displacements.data(), MPI_INT, + receives.data(), counts.data(), displacements.data(), + MPI_INT, MPI_COMM_WORLD, &request), + "MPI_Ialltoallv"); + require(MPI_Wait(&request, MPI_STATUS_IGNORE), "MPI_Wait(Ialltoallv)"); + +#if KAHIP_PMPI_SMOKE_HAVE_LARGE_COUNTS + auto large_counts = std::vector(static_cast(size), 1); + auto large_displacements = + std::vector(static_cast(size)); + for (int index = 0; index < size; ++index) { + large_displacements[static_cast(index)] = index; + } + require(MPI_Alltoallv_c(sends.data(), large_counts.data(), + large_displacements.data(), MPI_INT, receives.data(), + large_counts.data(), large_displacements.data(), + MPI_INT, MPI_COMM_WORLD), + "MPI_Alltoallv_c"); + require(MPI_Ialltoallv_c(sends.data(), large_counts.data(), + large_displacements.data(), MPI_INT, receives.data(), + large_counts.data(), large_displacements.data(), + MPI_INT, MPI_COMM_WORLD, &request), + "MPI_Ialltoallv_c"); + require(MPI_Wait(&request, MPI_STATUS_IGNORE), "MPI_Wait(Ialltoallv_c)"); +#endif + + auto const peer = (rank + 1) % size; + MPI_Comm neighborhood{MPI_COMM_NULL}; + require(MPI_Dist_graph_create_adjacent( + MPI_COMM_WORLD, 1, &peer, MPI_UNWEIGHTED, 1, &peer, + MPI_UNWEIGHTED, MPI_INFO_NULL, 0, &neighborhood), + "MPI_Dist_graph_create_adjacent"); + auto neighbor_send = rank; + auto neighbor_receive = -1; + std::array neighbor_counts{1}; + std::array neighbor_displacements{0}; + require(MPI_Neighbor_alltoall(&neighbor_send, 1, MPI_INT, &neighbor_receive, + 1, MPI_INT, neighborhood), + "MPI_Neighbor_alltoall"); + require(MPI_Neighbor_alltoallv(&neighbor_send, neighbor_counts.data(), + neighbor_displacements.data(), MPI_INT, + &neighbor_receive, neighbor_counts.data(), + neighbor_displacements.data(), MPI_INT, + neighborhood), + "MPI_Neighbor_alltoallv"); + require(MPI_Ineighbor_alltoallv(&neighbor_send, neighbor_counts.data(), + neighbor_displacements.data(), MPI_INT, + &neighbor_receive, neighbor_counts.data(), + neighbor_displacements.data(), MPI_INT, + neighborhood, &request), + "MPI_Ineighbor_alltoallv"); + require(MPI_Wait(&request, MPI_STATUS_IGNORE), + "MPI_Wait(Ineighbor_alltoallv)"); + +#if KAHIP_PMPI_SMOKE_HAVE_LARGE_COUNTS + std::array neighbor_large_counts{1}; + std::array neighbor_large_displacements{0}; + require(MPI_Neighbor_alltoallv_c( + &neighbor_send, neighbor_large_counts.data(), + neighbor_large_displacements.data(), MPI_INT, &neighbor_receive, + neighbor_large_counts.data(), neighbor_large_displacements.data(), + MPI_INT, neighborhood), + "MPI_Neighbor_alltoallv_c"); + require(MPI_Ineighbor_alltoallv_c( + &neighbor_send, neighbor_large_counts.data(), + neighbor_large_displacements.data(), MPI_INT, &neighbor_receive, + neighbor_large_counts.data(), neighbor_large_displacements.data(), + MPI_INT, neighborhood, &request), + "MPI_Ineighbor_alltoallv_c"); + require(MPI_Wait(&request, MPI_STATUS_IGNORE), + "MPI_Wait(Ineighbor_alltoallv_c)"); + +#endif + +#if KAHIP_PMPI_SMOKE_HAVE_PERSISTENT + require(MPI_Neighbor_alltoallv_init(&neighbor_send, neighbor_counts.data(), + neighbor_displacements.data(), MPI_INT, + &neighbor_receive, neighbor_counts.data(), + neighbor_displacements.data(), MPI_INT, + neighborhood, MPI_INFO_NULL, &request), + "MPI_Neighbor_alltoallv_init"); + for (int iteration = 0; iteration < 2; ++iteration) { + require(MPI_Start(&request), "MPI_Start"); + require(MPI_Wait(&request, MPI_STATUS_IGNORE), "MPI_Wait(persistent)"); + } + require(MPI_Request_free(&request), "MPI_Request_free"); +#endif + +#if KAHIP_PMPI_SMOKE_HAVE_PERSISTENT_C + std::array persistent_large_counts{1}; + std::array persistent_large_displacements{0}; + require( + MPI_Neighbor_alltoallv_init_c( + &neighbor_send, persistent_large_counts.data(), + persistent_large_displacements.data(), MPI_INT, &neighbor_receive, + persistent_large_counts.data(), persistent_large_displacements.data(), + MPI_INT, neighborhood, MPI_INFO_NULL, &request), + "MPI_Neighbor_alltoallv_init_c"); + for (int iteration = 0; iteration < 2; ++iteration) { + require(MPI_Startall(1, &request), "MPI_Startall"); + require(MPI_Wait(&request, MPI_STATUS_IGNORE), "MPI_Wait(persistent_c)"); + } + require(MPI_Request_free(&request), "MPI_Request_free(_c)"); +#endif + + require(MPI_Comm_free(&neighborhood), "MPI_Comm_free"); + require(MPI_Finalize(), "MPI_Finalize"); + return EXIT_SUCCESS; +} diff --git a/ci/performance/rank_runner.py b/ci/performance/rank_runner.py new file mode 100644 index 00000000..7ce9e476 --- /dev/null +++ b/ci/performance/rank_runner.py @@ -0,0 +1,209 @@ +"""Run one ParHIP MPI rank and record that rank's peak resident memory. + +The launcher deliberately uses ``fork`` + ``execvpe`` instead of +``subprocess``. MPI launchers commonly pass PMI/PMIx channels as inherited +file descriptors, and closing those descriptors between ``mpiexec`` and the +real rank can make MPI initialization fail or silently fall back. +""" + +from __future__ import annotations + +import argparse +import ctypes +import json +import os +from pathlib import Path +import resource +import signal +import socket +import sys +import time +from typing import Mapping, Sequence + + +_RANK_VARIABLES = ( + "OMPI_COMM_WORLD_RANK", + "PMI_RANK", + "PMIX_RANK", + "MV2_COMM_WORLD_RANK", + "SLURM_PROCID", +) + + +def resolve_rank(environment: Mapping[str, str]) -> int: + observed: dict[str, int] = {} + for name in _RANK_VARIABLES: + if name not in environment: + continue + text = environment[name] + try: + value = int(text, 10) + except ValueError as error: + raise ValueError(f"MPI rank variable {name} is not an integer") from error + if value < 0: + raise ValueError(f"MPI rank variable {name} is negative") + observed[name] = value + + if not observed: + raise ValueError("no MPI rank environment variable is available") + values = set(observed.values()) + if len(values) != 1: + details = ", ".join(f"{name}={value}" for name, value in observed.items()) + raise ValueError(f"MPI rank environment variables disagree: {details}") + return next(iter(values)) + + +def _exit_code(wait_status: int) -> tuple[int, int | None]: + if os.WIFEXITED(wait_status): + return os.WEXITSTATUS(wait_status), None + if os.WIFSIGNALED(wait_status): + signal_number = os.WTERMSIG(wait_status) + return 128 + signal_number, signal_number + return 125, None + + +def _rss_bytes(usage: resource.struct_rusage) -> int: + maximum = int(usage.ru_maxrss) + return maximum if sys.platform == "darwin" else maximum * 1024 + + +def _write_metrics(path: Path, metrics: Mapping[str, object]) -> None: + if path.exists(): + raise FileExistsError(f"rank metrics already exist: {path}") + temporary = path.with_name(f".{path.name}.{os.getpid()}.tmp") + with temporary.open("x", encoding="utf-8") as output: + json.dump(metrics, output, sort_keys=True, allow_nan=False) + output.write("\n") + output.flush() + os.fsync(output.fileno()) + try: + os.link(temporary, path) + finally: + temporary.unlink(missing_ok=True) + + +def _install_parent_death_signal(parent_pid: int) -> None: + if not sys.platform.startswith("linux"): + return + libc = ctypes.CDLL(None, use_errno=True) + pr_set_pdeathsig = 1 + if libc.prctl(pr_set_pdeathsig, signal.SIGTERM, 0, 0, 0) != 0: + error_number = ctypes.get_errno() + raise OSError(error_number, os.strerror(error_number)) + if os.getppid() != parent_pid: + os.kill(os.getpid(), signal.SIGTERM) + + +def _cpu_affinity() -> tuple[list[int], str]: + if hasattr(os, "sched_getaffinity"): + return sorted(os.sched_getaffinity(0)), "sched_getaffinity" + count = os.cpu_count() or 1 + return list(range(count)), "logical-cpu-fallback" + + +def run_rank( + command: Sequence[str], + *, + metrics_directory: Path, + environment: Mapping[str, str], + preload: Path | None = None, +) -> int: + if not command: + raise ValueError("rank command must not be empty") + if preload is not None and not preload.is_file(): + raise FileNotFoundError(f"rank preload library does not exist: {preload}") + rank = resolve_rank(environment) + metrics_directory.mkdir(parents=True, exist_ok=True) + metrics_path = metrics_directory / f"rank-{rank}.json" + started_ns = time.monotonic_ns() + affinity, affinity_source = _cpu_affinity() + parent_pid = os.getpid() + child = os.fork() + if child == 0: + try: + for signal_number in (signal.SIGHUP, signal.SIGINT, signal.SIGTERM): + signal.signal(signal_number, signal.SIG_DFL) + _install_parent_death_signal(parent_pid) + child_environment = dict(environment) + if preload is not None: + prior_preload = child_environment.get("LD_PRELOAD") + child_environment["LD_PRELOAD"] = str(preload.resolve()) + if prior_preload: + child_environment["LD_PRELOAD"] += f":{prior_preload}" + os.execvpe(command[0], list(command), child_environment) + except BaseException as error: # nothing may unwind across fork/exec + message = f"cannot exec MPI rank command: {error}\n".encode( + "utf-8", errors="replace" + ) + try: + os.write(2, message) + finally: + os._exit(127) + + previous_handlers = {} + + def forward_signal(signal_number: int, _frame: object) -> None: + try: + os.kill(child, signal_number) + except ProcessLookupError: + pass + + for signal_number in (signal.SIGHUP, signal.SIGINT, signal.SIGTERM): + previous_handlers[signal_number] = signal.signal( + signal_number, forward_signal + ) + try: + while True: + try: + _, wait_status, usage = os.wait4(child, 0) + break + except InterruptedError: + continue + finally: + for signal_number, previous in previous_handlers.items(): + signal.signal(signal_number, previous) + finished_ns = time.monotonic_ns() + return_code, signal_number = _exit_code(wait_status) + _write_metrics( + metrics_path, + { + "schema_version": 1, + "rank": rank, + "hostname": socket.gethostname(), + "child_pid": child, + "return_code": return_code, + "signal": signal_number, + "elapsed_nanoseconds": finished_ns - started_ns, + "max_rss_bytes": _rss_bytes(usage), + "cpu_affinity": affinity, + "cpu_affinity_source": affinity_source, + }, + ) + return return_code + + +def main(arguments: Sequence[str] | None = None) -> int: + parser = argparse.ArgumentParser( + description="launch one MPI rank without closing PMI/PMIx descriptors" + ) + parser.add_argument("--metrics-directory", type=Path, required=True) + parser.add_argument("--preload", type=Path) + parser.add_argument("command", nargs=argparse.REMAINDER) + options = parser.parse_args(arguments) + command = list(options.command) + if command and command[0] == "--": + command.pop(0) + try: + return run_rank( + command, + metrics_directory=options.metrics_directory, + environment=os.environ, + preload=options.preload, + ) + except (OSError, ValueError) as error: + print(f"rank measurement failed: {error}", file=sys.stderr) + return 125 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/ci/performance/tests/fixtures/parhip-output.log b/ci/performance/tests/fixtures/parhip-output.log new file mode 100644 index 00000000..d5613a9d --- /dev/null +++ b/ci/performance/tests/fixtures/parhip-output.log @@ -0,0 +1,19 @@ +running collective dummy operations took 0.125 +Reading binary graph ... +version: 3 n: 1000 m: 5400 +took 0.400 +log>cycle: 0 level: 1 parallel label compression took 1.0e-1 +log>cycle: 0 level: 1 contraction took 0.20 +log>cycle: 0 level: 2 parallel label compression took 0.15 +log>cycle: 0 level: 2 contraction took 0.30 +log>cycle: 0 coarsening took 0.75 +log>cycle: 0 initial partitioning took 0.50 +log>cycle: 0 level: 2 projection took 0.11 +log>cycle: 0 level: 2 label compression refinement took 0.12 +log>cycle: 0 level: 1 projection took 0.13 +log>cycle: 0 level: 1 label compression refinement took 0.14 +log>cycle: 0 uncoarsening took 1.70 +log>cycle: 0 k 4 cut 41 balance 1.02 time 2.40 +log>total partitioning time elapsed 2.5 +log>final edge cut 41 +log>final balance 1.02 diff --git a/ci/performance/tests/test_harness.py b/ci/performance/tests/test_harness.py new file mode 100644 index 00000000..0413ea35 --- /dev/null +++ b/ci/performance/tests/test_harness.py @@ -0,0 +1,1144 @@ +from __future__ import annotations + +import json +import os +from pathlib import Path +import subprocess +import sys +import tempfile +import time +import unittest + +from ci.performance import parhip_harness +from ci.performance import rank_runner + + +FIXTURES = Path(__file__).with_name("fixtures") + + +def synthetic_record( + variant: str, + *, + fixture: str = "cube100", + ranks: int = 4, + seed: int = 1, + repetition: int = 0, + cut: int = 100, + runtime: float = 10.0, + rss: int = 1_000_000, +) -> dict[str, object]: + return { + "variant": variant, + "case": { + "fixture": fixture, + "ranks": ranks, + "blocks": 4, + "preconfiguration": "fastmesh", + "imbalance_percent": 3, + "seed": seed, + "repetition": repetition, + }, + "end_to_end_seconds": runtime, + "max_rank_rss_bytes": rss, + "rank_placement": [ + {"rank": rank, "hostname": "node-a", "cpu_affinity": [rank]} + for rank in range(ranks) + ], + "parhip": { + "final_cut": cut, + "stage_events": [ + { + "cycle": 0, + "level": None, + "stage": "coarsening_total", + "seconds": 1.0, + } + ], + }, + "verification": { + "balanced": True, + "vertices": 1_000_000, + "blocks": 4, + "maximum_block_weight": 257_500, + "block_weights": [250_000, 250_000, 250_000, 250_000], + "weighted_cut": cut, + }, + } + + +def synthetic_config() -> dict[str, object]: + return { + "schema_version": 1, + "pinned_upstream_revision": "a" * 40, + "run_limited": "/repo/ci/run-limited", + "mpiexec": { + "executable": "/usr/bin/mpiexec", + "numproc_flag": "-n", + "preflags": ["--bind-to", "core"], + "postflags": [], + }, + "variants": { + "baseline": { + "source_directory": "/src/baseline", + "build_directory": "/build/baseline", + "executable": "/build/baseline/parhip", + "configure_command": ["cmake", "--preset", "release"], + "build_command": ["cmake", "--build", "--preset", "release"], + }, + "candidate": { + "source_directory": "/src/candidate", + "build_directory": "/build/candidate", + "executable": "/build/candidate/parhip", + "configure_command": ["cmake", "--preset", "release"], + "build_command": ["cmake", "--build", "--preset", "release"], + }, + }, + "fixtures": [ + { + "name": "cube4", + "dimensions": [4, 4, 4], + "generator": "/build/candidate/kahip_cube_generator", + "verifier": "/build/candidate/kahip_cube_partition_verify", + } + ], + "matrix": { + "seeds": [1, 2], + "ranks": [2, 4], + "blocks": [4], + "preconfigurations": ["fastmesh"], + "imbalance_percent": [3], + "repetitions": 2, + }, + "bootstrap": {"iterations": 1_000, "seed": 17, "min_pairs": 4}, + "output_directory": "/results", + "timeout_seconds": 3_600, + "concurrency": 2, + } + + +class OutputParserTests(unittest.TestCase): + def test_parser_preserves_stage_events_and_sums_repeated_levels(self) -> None: + # Break caught: accepting only the last recursive level loses stage time. + parsed = parhip_harness.parse_parhip_output( + (FIXTURES / "parhip-output.log").read_text(encoding="utf-8") + ) + + self.assertEqual(parsed["final_cut"], 41) + self.assertEqual(parsed["final_balance"], 1.02) + self.assertEqual(parsed["partition_seconds"], 2.5) + self.assertEqual(parsed["startup_dummy_seconds"], 0.125) + self.assertEqual(parsed["input_ready_elapsed_seconds"], 0.4) + self.assertAlmostEqual(parsed["stage_totals_seconds"]["contraction"], 0.5) + self.assertAlmostEqual( + parsed["stage_totals_seconds"]["label_compression_coarsening"], + 0.25, + ) + self.assertEqual(len(parsed["stage_events"]), 11) + self.assertEqual( + parsed["stage_events"][0], + { + "cycle": 0, + "level": 1, + "stage": "label_compression_coarsening", + "seconds": 0.1, + }, + ) + + def test_parser_rejects_output_without_required_final_metrics(self) -> None: + # Break caught: a crashed/truncated run must not become a benchmark sample. + with self.assertRaisesRegex( + parhip_harness.ParseError, "missing final partition metrics" + ): + parhip_harness.parse_parhip_output( + "log>cycle: 0 level: 1 contraction took 0.2\n" + ) + + def test_verifier_parser_requires_the_canonical_invariant_record(self) -> None: + # Break caught: trusting ParHIP's self-reported cut instead of the verifier. + parsed = parhip_harness.parse_verifier_output( + "verified vertices=64 blocks=2 maximum-block-weight=33 " + "block-weights=[32,32] weighted-cut=28\n" + ) + self.assertEqual( + parsed, + { + "balanced": True, + "vertices": 64, + "blocks": 2, + "maximum_block_weight": 33, + "block_weights": [32, 32], + "weighted_cut": 28, + }, + ) + with self.assertRaises(parhip_harness.ParseError): + parhip_harness.parse_verifier_output( + "verified vertices=64 blocks=2 weighted-cut=28\n" + ) + + def test_parser_rejects_duplicate_setup_times_and_truncated_stages(self) -> None: + # Break caught: accepting a partial or concatenated log as a complete run. + duplicate = (FIXTURES / "parhip-output.log").read_text(encoding="utf-8") + duplicate += "running collective dummy operations took 0.250\n" + with self.assertRaisesRegex(parhip_harness.ParseError, "duplicate"): + parhip_harness.parse_parhip_output(duplicate) + + with self.assertRaisesRegex(parhip_harness.ParseError, "stage timing"): + parhip_harness.parse_parhip_output( + "log>cycle: 0 level: 1 contraction took 0.2\n" + "log>total partitioning time elapsed 0.2\n" + "log>final edge cut 1\n" + "log>final balance 1.0\n" + ) + + +class SchedulingAndCommandTests(unittest.TestCase): + def test_pair_order_alternates_ab_then_ba(self) -> None: + # Break caught: always running baseline first biases paired timings. + self.assertEqual( + [parhip_harness.alternating_variant_order(index) for index in range(4)], + [ + ("baseline", "candidate"), + ("candidate", "baseline"), + ("baseline", "candidate"), + ("candidate", "baseline"), + ], + ) + + def test_every_generated_operation_is_prefixed_by_run_limited(self) -> None: + # Break caught: bypassing the requested cgroup wrapper for one operation. + wrapper = Path("/repo/ci/run-limited") + command = parhip_harness.limited_command( + wrapper, ["mpiexec", "-n", "4", "parhip"] + ) + self.assertEqual( + command, + ["/repo/ci/run-limited", "mpiexec", "-n", "4", "parhip"], + ) + + def test_benchmark_command_wraps_each_rank_without_moving_mpi_preflags( + self, + ) -> None: + # Break caught: putting the Python wrapper before mpiexec measures the + # launcher, while putting MPI postflags after it changes ParHIP argv. + case = { + "fixture": "cube100", + "ranks": 4, + "blocks": 4, + "preconfiguration": "fastmesh", + "imbalance_percent": 3, + "seed": 1, + "repetition": 0, + } + command = parhip_harness.build_benchmark_command( + run_limited=Path("/repo/ci/run-limited"), + mpiexec=Path("/usr/bin/mpiexec"), + numproc_flag="-n", + mpi_preflags=["--bind-to", "core"], + python_executable=Path("/usr/bin/python3"), + rank_runner=Path("/repo/ci/performance/rank_runner.py"), + rank_metrics_directory=Path("/results/ranks"), + parhip=Path("/build/parhip"), + graph=Path("/results/cube100.graph"), + case=case, + ) + self.assertEqual( + command, + [ + "/repo/ci/run-limited", + "/usr/bin/mpiexec", + "-n", + "4", + "--bind-to", + "core", + "/usr/bin/python3", + "/repo/ci/performance/rank_runner.py", + "--metrics-directory", + "/results/ranks", + "--", + "/build/parhip", + "/results/cube100.graph", + "--k=4", + "--preconfiguration=fastmesh", + "--seed=1", + "--imbalance=3", + "--save_partition", + ], + ) + instrumented = parhip_harness.build_benchmark_command( + run_limited=Path("/repo/ci/run-limited"), + mpiexec=Path("/usr/bin/mpiexec"), + numproc_flag="-n", + mpi_preflags=[], + python_executable=Path("/usr/bin/python3"), + rank_runner=Path("/repo/ci/performance/rank_runner.py"), + rank_metrics_directory=Path("/results/ranks"), + parhip=Path("/build/parhip"), + graph=Path("/results/cube100.graph"), + case=case, + collective_bytes_interposer=Path("/tools/pmpi-bytes.so"), + ) + separator = instrumented.index("--") + self.assertEqual( + instrumented[separator - 2 : separator], + ["--preload", "/tools/pmpi-bytes.so"], + ) + self.assertNotIn("LD_PRELOAD", instrumented) + + def test_case_matrix_is_deterministic_and_keeps_repetitions_paired(self) -> None: + # Break caught: independently shuffling variants destroys paired samples. + cases = parhip_harness.enumerate_cases(synthetic_config()) + self.assertEqual(len(cases), 8) + self.assertEqual( + cases[0], + { + "fixture": "cube4", + "ranks": 2, + "blocks": 4, + "preconfiguration": "fastmesh", + "imbalance_percent": 3, + "seed": 1, + "repetition": 0, + }, + ) + self.assertEqual(cases[1]["repetition"], 1) + self.assertEqual(cases[-1]["ranks"], 4) + + +class ConfigurationTests(unittest.TestCase): + def test_valid_config_is_accepted_but_mpi_postflags_are_rejected(self) -> None: + # Break caught: CMake-style postflags would land between the rank wrapper + # and ParHIP and could be interpreted as application arguments. + parhip_harness.validate_config(synthetic_config()) + invalid = synthetic_config() + invalid["mpiexec"] = dict(invalid["mpiexec"], postflags=["--oversubscribe"]) + with self.assertRaisesRegex(ValueError, "postflags.*empty"): + parhip_harness.validate_config(invalid) + + def test_config_rejects_unknown_schema_and_more_than_two_jobs(self) -> None: + # Break caught: silently accepting a newer schema or excessive concurrency. + invalid_schema = synthetic_config() + invalid_schema["schema_version"] = 2 + with self.assertRaisesRegex(ValueError, "schema_version"): + parhip_harness.validate_config(invalid_schema) + + excessive = synthetic_config() + excessive["concurrency"] = 3 + with self.assertRaisesRegex(ValueError, "concurrency"): + parhip_harness.validate_config(excessive) + + def test_explicit_build_parallelism_cannot_exceed_configured_cap(self) -> None: + # Break caught: CMAKE_BUILD_PARALLEL_LEVEL=2 cannot override `-j32`. + parhip_harness.validate_job_cap( + ["cmake", "--build", "--preset", "release", "--parallel", "2"], + cap=2, + ) + for command in ( + ["cmake", "--build", ".", "-j32"], + ["cmake", "--build", ".", "--parallel=4"], + ["cmake", "--build", ".", "--parallel", "8"], + ): + with self.assertRaisesRegex(ValueError, "parallelism"): + parhip_harness.validate_job_cap(command, cap=2) + + unbounded = synthetic_config() + unbounded["variants"]["candidate"]["build_command"] = ["ninja"] + with self.assertRaisesRegex(ValueError, "explicit bounded parallelism"): + parhip_harness.validate_config(unbounded) + + bounded = synthetic_config() + bounded["variants"]["candidate"]["build_command"] = ["ninja", "-j2"] + parhip_harness.validate_config(bounded) + + +class BootstrapAndGateTests(unittest.TestCase): + def test_bootstrap_estimate_is_ratio_of_medians_not_median_of_ratios(self) -> None: + # Break caught: the two estimators differ materially for heterogeneous pairs. + result = parhip_harness.bootstrap_ratio_of_medians( + [(1.0, 2.0), (10.0, 90.0), (100.0, 110.0)], + iterations=2_000, + seed=17, + ) + self.assertEqual(result["estimate"], 9.0) + self.assertLessEqual(result["lower"], result["estimate"]) + self.assertGreaterEqual(result["upper"], result["estimate"]) + self.assertEqual( + result, + parhip_harness.bootstrap_ratio_of_medians( + [(1.0, 2.0), (10.0, 90.0), (100.0, 110.0)], + iterations=2_000, + seed=17, + ), + ) + + def test_constant_scaling_has_an_exact_bootstrap_interval(self) -> None: + # Break caught: resampling baseline and candidate independently breaks pairing. + result = parhip_harness.bootstrap_ratio_of_medians( + [(10.0, 10.4), (20.0, 20.8), (30.0, 31.2), (40.0, 41.6)], + iterations=500, + seed=3, + ) + self.assertAlmostEqual(result["estimate"], 1.04) + self.assertAlmostEqual(result["lower"], 1.04) + self.assertAlmostEqual(result["upper"], 1.04) + + def test_acceptance_passes_only_when_all_quality_and_ci_gates_pass(self) -> None: + # Break caught: reporting success from runtime while ignoring cut/RSS/balance. + records: list[dict[str, object]] = [] + for repetition in range(6): + records.append( + synthetic_record( + "baseline", + repetition=repetition, + seed=1 + repetition % 2, + runtime=10.0 + repetition, + rss=1_000_000 + repetition * 1_000, + ) + ) + records.append( + synthetic_record( + "candidate", + repetition=repetition, + seed=1 + repetition % 2, + runtime=(10.0 + repetition) * 1.04, + rss=int((1_000_000 + repetition * 1_000) * 1.03), + ) + ) + + result = parhip_harness.evaluate_acceptance( + records, bootstrap_iterations=1_000, bootstrap_seed=9, min_pairs=6 + ) + + self.assertTrue(result["passed"]) + self.assertTrue(result["gates"]["balanced_partitions"]["passed"]) + self.assertTrue(result["gates"]["aggregate_cut"]["passed"]) + self.assertTrue(result["gates"]["per_configuration_cut"]["passed"]) + self.assertLessEqual( + result["gates"]["runtime_ci"]["upper"], 1.05 + ) + self.assertLessEqual(result["gates"]["rss_ci"]["upper"], 1.05) + + def test_per_configuration_cut_can_fail_while_aggregate_cut_passes(self) -> None: + # Break caught: an aggregate improvement must not hide one 4% regression. + records = [ + synthetic_record("baseline", fixture="cube-a", cut=100), + synthetic_record("candidate", fixture="cube-a", cut=104), + synthetic_record("baseline", fixture="cube-b", cut=100), + synthetic_record("candidate", fixture="cube-b", cut=96), + ] + result = parhip_harness.evaluate_acceptance( + records, bootstrap_iterations=100, bootstrap_seed=1, min_pairs=2 + ) + self.assertTrue(result["gates"]["aggregate_cut"]["passed"]) + self.assertFalse(result["gates"]["per_configuration_cut"]["passed"]) + self.assertFalse(result["passed"]) + + def test_positive_candidate_cut_fails_against_zero_baseline(self) -> None: + # Break caught: serializing infinity/NaN or treating division by zero as pass. + records = [ + synthetic_record("baseline", cut=0), + synthetic_record("candidate", cut=1), + ] + result = parhip_harness.evaluate_acceptance( + records, bootstrap_iterations=100, bootstrap_seed=1, min_pairs=1 + ) + self.assertFalse(result["gates"]["aggregate_cut"]["passed"]) + self.assertIsNone(result["gates"]["aggregate_cut"]["ratio"]) + json.dumps(result, allow_nan=False) + + def test_upper_confidence_bound_not_point_estimate_controls_runtime_gate( + self, + ) -> None: + # Break caught: an apparently favorable median can hide an unstable tail. + candidate_times = [0.5, 0.5, 1.5] + records = [] + for repetition, candidate_time in enumerate(candidate_times): + records.extend( + [ + synthetic_record( + "baseline", repetition=repetition, runtime=1.0 + ), + synthetic_record( + "candidate", + repetition=repetition, + runtime=candidate_time, + ), + ] + ) + result = parhip_harness.evaluate_acceptance( + records, bootstrap_iterations=2_000, bootstrap_seed=2, min_pairs=3 + ) + self.assertLessEqual(result["gates"]["runtime_ci"]["estimate"], 1.05) + self.assertGreater(result["gates"]["runtime_ci"]["upper"], 1.05) + self.assertFalse(result["gates"]["runtime_ci"]["passed"]) + + def test_expected_matrix_rejects_wholly_missing_and_unplanned_pairs(self) -> None: + # Break caught: removing both variants of the slowest tuple must not pass. + planned = [ + synthetic_record("baseline", repetition=index)["case"] + for index in range(2) + ] + records = [ + synthetic_record("baseline", repetition=0), + synthetic_record("candidate", repetition=0), + ] + with self.assertRaisesRegex(ValueError, "missing planned"): + parhip_harness.evaluate_acceptance( + records, + bootstrap_iterations=100, + bootstrap_seed=1, + min_pairs=1, + expected_cases=planned, + ) + with self.assertRaisesRegex(ValueError, "unplanned"): + parhip_harness.evaluate_acceptance( + [ + *records, + synthetic_record("baseline", fixture="unplanned"), + synthetic_record("candidate", fixture="unplanned"), + ], + bootstrap_iterations=100, + bootstrap_seed=1, + min_pairs=1, + expected_cases=[planned[0]], + ) + + def test_rank_placement_must_match_within_each_pair(self) -> None: + # Break caught: comparing different host/core placement as if paired. + baseline = synthetic_record("baseline") + candidate = synthetic_record("candidate") + candidate["rank_placement"][2]["cpu_affinity"] = [63] + result = parhip_harness.evaluate_acceptance( + [baseline, candidate], + bootstrap_iterations=100, + bootstrap_seed=1, + min_pairs=1, + ) + self.assertFalse(result["gates"]["rank_placement"]["passed"]) + self.assertFalse(result["passed"]) + + +class RankMeasurementTests(unittest.TestCase): + def test_rank_environment_accepts_known_names_and_rejects_disagreement(self) -> None: + # Break caught: attributing one rank's RSS to another rank. + self.assertEqual(rank_runner.resolve_rank({"OMPI_COMM_WORLD_RANK": "3"}), 3) + self.assertEqual(rank_runner.resolve_rank({"PMI_RANK": "2"}), 2) + self.assertEqual(rank_runner.resolve_rank({"PMIX_RANK": "1"}), 1) + self.assertEqual( + rank_runner.resolve_rank( + {"OMPI_COMM_WORLD_RANK": "4", "PMI_RANK": "4"} + ), + 4, + ) + with self.assertRaisesRegex(ValueError, "disagree"): + rank_runner.resolve_rank( + {"OMPI_COMM_WORLD_RANK": "0", "PMI_RANK": "1"} + ) + with self.assertRaisesRegex(ValueError, "no MPI rank"): + rank_runner.resolve_rank({}) + + def test_rank_runner_preserves_inheritable_descriptors_and_records_wait4_rss( + self, + ) -> None: + # Break caught: subprocess close_fds severs PMI/PMIx descriptors. + with tempfile.TemporaryDirectory() as directory: + read_fd, write_fd = os.pipe() + os.set_inheritable(write_fd, True) + try: + environment = dict(os.environ) + environment["OMPI_COMM_WORLD_RANK"] = "0" + environment["KAHIP_TEST_INHERITED_FD"] = str(write_fd) + command = [ + sys.executable, + "-c", + "import os; os.write(int(os.environ['KAHIP_TEST_INHERITED_FD']), b'ok')", + ] + result = rank_runner.run_rank( + command, + metrics_directory=Path(directory), + environment=environment, + ) + os.close(write_fd) + write_fd = -1 + self.assertEqual(os.read(read_fd, 2), b"ok") + finally: + os.close(read_fd) + if write_fd >= 0: + os.close(write_fd) + + self.assertEqual(result, 0) + metrics = json.loads( + (Path(directory) / "rank-0.json").read_text(encoding="utf-8") + ) + self.assertEqual(metrics["rank"], 0) + self.assertEqual(metrics["return_code"], 0) + self.assertGreater(metrics["max_rss_bytes"], 0) + self.assertTrue(metrics["cpu_affinity"]) + + def test_rank_runner_refuses_to_replace_an_existing_rank_record(self) -> None: + # Break caught: two writers for one rank silently replace evidence. + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + environment = dict(os.environ, OMPI_COMM_WORLD_RANK="0") + self.assertEqual( + rank_runner.run_rank( + [sys.executable, "-c", "pass"], + metrics_directory=root, + environment=environment, + ), + 0, + ) + with self.assertRaises(FileExistsError): + rank_runner.run_rank( + [sys.executable, "-c", "pass"], + metrics_directory=root, + environment=environment, + ) + + def test_rank_runner_forwards_termination_to_its_parhip_child(self) -> None: + # Break caught: a launcher timeout must not orphan a continuing rank. + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + child_pid_path = root / "child.pid" + environment = dict(os.environ, OMPI_COMM_WORLD_RANK="0") + process = subprocess.Popen( + [ + sys.executable, + str(Path(rank_runner.__file__)), + "--metrics-directory", + str(root / "metrics"), + "--", + sys.executable, + "-c", + ( + "import os,time,pathlib; " + f"pathlib.Path({str(child_pid_path)!r}).write_text(str(os.getpid())); " + "time.sleep(30)" + ), + ], + env=environment, + ) + deadline = time.monotonic() + 5 + while not child_pid_path.exists() and time.monotonic() < deadline: + time.sleep(0.01) + self.assertTrue(child_pid_path.exists()) + child_pid = int(child_pid_path.read_text(encoding="utf-8")) + process.terminate() + self.assertEqual(process.wait(timeout=5), 128 + 15) + metrics = json.loads( + (root / "metrics" / "rank-0.json").read_text(encoding="utf-8") + ) + self.assertEqual(metrics["return_code"], 128 + 15) + with self.assertRaises(ProcessLookupError): + os.kill(child_pid, 0) + + def test_rank_runner_applies_preload_only_to_the_exec_child(self) -> None: + # Break caught: preloading mpiexec/the wrapper can create false rank files. + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + preload = next( + ( + path + for path in ( + Path("/usr/lib64/libm.so.6"), + Path("/lib/x86_64-linux-gnu/libm.so.6"), + Path("/usr/lib/x86_64-linux-gnu/libm.so.6"), + ) + if path.is_file() + ), + None, + ) + if preload is None: + self.skipTest("no harmless shared library is available") + observed = root / "observed.txt" + environment = dict(os.environ, OMPI_COMM_WORLD_RANK="0") + original = os.environ.get("LD_PRELOAD") + result = rank_runner.run_rank( + [ + sys.executable, + "-c", + ( + "import os,pathlib; " + f"pathlib.Path({str(observed)!r}).write_text(os.environ['LD_PRELOAD'])" + ), + ], + metrics_directory=root / "metrics", + environment=environment, + preload=preload, + ) + self.assertEqual(result, 0) + self.assertEqual(observed.read_text(encoding="utf-8"), str(preload)) + self.assertEqual(os.environ.get("LD_PRELOAD"), original) + + def test_rank_metric_aggregation_uses_maximum_rank_not_a_launcher_value( + self, + ) -> None: + # Break caught: summing rank RSS or reading mpiexec's RSS instead. + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + for rank, rss in enumerate((100, 700, 250)): + (root / f"rank-{rank}.json").write_text( + json.dumps( + { + "rank": rank, + "return_code": 0, + "max_rss_bytes": rss, + "hostname": "node-a", + "cpu_affinity": [rank], + } + ), + encoding="utf-8", + ) + result = parhip_harness.read_rank_metrics(root, expected_ranks=3) + self.assertEqual(result["max_rank_rss_bytes"], 700) + self.assertEqual(result["per_rank_rss_bytes"], [100, 700, 250]) + self.assertEqual(result["rank_placement"][2]["cpu_affinity"], [2]) + + +class PerRunValidationTests(unittest.TestCase): + def test_partition_metrics_must_match_fixture_case_and_parhip_cut(self) -> None: + # Break caught: accepting the verifier record for a different graph/k. + case = synthetic_record("baseline")["case"] + fixture = {"name": "cube100", "dimensions": [100, 100, 100]} + parsed = {"final_cut": 41} + verification = { + "balanced": True, + "vertices": 1_000_000, + "blocks": 4, + "weighted_cut": 41, + } + parhip_harness.validate_partition_metrics( + case=case, + fixture=fixture, + parhip=parsed, + verification=verification, + ) + with self.assertRaisesRegex(ValueError, "self-reported cut"): + parhip_harness.validate_partition_metrics( + case=case, + fixture=fixture, + parhip={"final_cut": 42}, + verification=verification, + ) + with self.assertRaisesRegex(ValueError, "vertex count"): + parhip_harness.validate_partition_metrics( + case=case, + fixture=fixture, + parhip=parsed, + verification=dict(verification, vertices=64), + ) + with self.assertRaisesRegex(ValueError, "block count"): + parhip_harness.validate_partition_metrics( + case=case, + fixture=fixture, + parhip=parsed, + verification=dict(verification, blocks=2), + ) + + def test_run_directory_must_be_new_so_partition_cannot_be_stale(self) -> None: + # Break caught: a failed ParHIP launch must not reuse tmppartition.txtp. + with tempfile.TemporaryDirectory() as directory: + run_directory = Path(directory) / "run" + parhip_harness.create_run_directory(run_directory) + (run_directory / "tmppartition.txtp").write_text( + "stale\n", encoding="utf-8" + ) + with self.assertRaises(FileExistsError): + parhip_harness.create_run_directory(run_directory) + + def test_synthetic_run_records_logs_rank_rss_and_fresh_verification(self) -> None: + # Break caught: a runner that never invokes the verifier can appear valid. + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + graph = root / "cube4.graph" + graph.write_text("synthetic graph\n", encoding="utf-8") + calls: list[list[str]] = [] + + def fake_executor( + command: list[str], + *, + cwd: Path, + environment: dict[str, str], + timeout_seconds: float, + ) -> parhip_harness.CommandResult: + del environment, timeout_seconds + calls.append(command) + if "rank_runner.py" in " ".join(command): + (cwd / "tmppartition.txtp").write_text( + "0\n" * 64, encoding="utf-8" + ) + metrics_flag = command.index("--metrics-directory") + metrics = Path(command[metrics_flag + 1]) + metrics.mkdir() + for rank in range(2): + (metrics / f"rank-{rank}.json").write_text( + json.dumps( + { + "rank": rank, + "return_code": 0, + "max_rss_bytes": 100 + rank, + "hostname": "node-a", + "cpu_affinity": [rank], + } + ), + encoding="utf-8", + ) + return parhip_harness.CommandResult( + return_code=0, + stdout=(FIXTURES / "parhip-output.log").read_text( + encoding="utf-8" + ), + stderr="", + elapsed_seconds=3.5, + ) + return parhip_harness.CommandResult( + return_code=0, + stdout=( + "verified vertices=64 blocks=4 maximum-block-weight=17 " + "block-weights=[16,16,16,16] weighted-cut=41\n" + ), + stderr="", + elapsed_seconds=0.01, + ) + + fixture = { + "name": "cube4", + "dimensions": [4, 4, 4], + "verifier": "/tools/verifier", + "graph_path": str(graph), + } + case = { + "fixture": "cube4", + "ranks": 2, + "blocks": 4, + "preconfiguration": "fastmesh", + "imbalance_percent": 3, + "seed": 1, + "repetition": 0, + } + record = parhip_harness.execute_one_run( + variant="candidate", + case=case, + fixture=fixture, + graph=graph, + parhip=Path("/build/parhip"), + run_directory=root / "run", + run_limited=Path("/repo/ci/run-limited"), + mpiexec=Path("/usr/bin/mpiexec"), + numproc_flag="-n", + mpi_preflags=[], + python_executable=Path("/usr/bin/python3"), + rank_runner=Path("/repo/ci/performance/rank_runner.py"), + environment={}, + timeout_seconds=60, + executor=fake_executor, + ) + + self.assertEqual(len(calls), 2) + self.assertTrue(all(call[0] == "/repo/ci/run-limited" for call in calls)) + self.assertEqual(record["verification"]["weighted_cut"], 41) + self.assertEqual(record["max_rank_rss_bytes"], 101) + self.assertEqual(record["end_to_end_seconds"], 3.5) + self.assertEqual(record["partition_sha256"], record["artifacts"]["partition_sha256"]) + self.assertIn("stdout_sha256", record["artifacts"]) + + +class BuildAndGitProvenanceTests(unittest.TestCase): + def test_stage_timing_requires_matching_output_enabled_release_builds(self) -> None: + # Break caught: comparing a NOOUTPUT build with no parseable stage records. + baseline = { + "build_type": "Release", + "optimized_output": "ON", + "compiler": "/usr/bin/g++", + "compiler_version": "g++ 16.2.1", + "release_flags": "-O3 -DNDEBUG", + "link_flags": "-Wl,--as-needed", + "cmake_generator": "Ninja", + "cmake_version": "cmake version 4.1.0", + "mpi_executable": "/usr/bin/mpiexec", + "mpi_version": "Open MPI 5.0.10", + "mpi_cache_identity": {"MPI_CXX_COMPILER": "/usr/bin/mpicxx"}, + "linked_mpi_libraries": [ + { + "name": "libmpi.so.40", + "path": "/usr/lib/libmpi.so.40", + "sha256": "e" * 64, + } + ], + } + parhip_harness.validate_build_equivalence( + baseline, dict(baseline), require_stage_timings=True + ) + disabled = dict(baseline, optimized_output="OFF") + with self.assertRaisesRegex(ValueError, "OPTIMIZED_OUTPUT=ON"): + parhip_harness.validate_build_equivalence( + baseline, disabled, require_stage_timings=True + ) + debug = dict(baseline, build_type="Debug") + with self.assertRaisesRegex(ValueError, "Release"): + parhip_harness.validate_build_equivalence( + baseline, debug, require_stage_timings=True + ) + + def test_git_provenance_distinguishes_pristine_baseline_and_dirty_candidate( + self, + ) -> None: + # Break caught: accepting a dirty baseline or omitting candidate dirtiness. + with tempfile.TemporaryDirectory() as directory: + repository = Path(directory) + subprocess.run(["git", "init", "-q", repository], check=True) + tracked = repository / "tracked.txt" + tracked.write_text("baseline\n", encoding="utf-8") + subprocess.run(["git", "-C", repository, "add", "tracked.txt"], check=True) + subprocess.run( + [ + "git", + "-C", + repository, + "-c", + "user.name=Harness Test", + "-c", + "user.email=harness@example.invalid", + "-c", + "commit.gpgsign=false", + "commit", + "-qm", + "baseline", + ], + check=True, + ) + baseline = parhip_harness.collect_git_provenance(repository) + parhip_harness.validate_pristine_baseline( + baseline, baseline["revision"] + ) + + tracked.write_text("candidate\n", encoding="utf-8") + (repository / "new.txt").write_text("untracked\n", encoding="utf-8") + candidate = parhip_harness.collect_git_provenance(repository) + + self.assertFalse(candidate["clean"]) + self.assertNotEqual(candidate["diff_sha256"], baseline["diff_sha256"]) + self.assertEqual(candidate["untracked_files"], ["new.txt"]) + self.assertIsNotNone(candidate["untracked_manifest_sha256"]) + with self.assertRaisesRegex(ValueError, "not pristine"): + parhip_harness.validate_pristine_baseline( + candidate, baseline["revision"] + ) + + def test_cmake_cache_provenance_uses_effective_release_flags(self) -> None: + # Break caught: comparing only CMAKE_CXX_FLAGS_RELEASE misses common flags. + with tempfile.TemporaryDirectory() as directory: + cache = Path(directory) / "CMakeCache.txt" + cache.write_text( + "CMAKE_BUILD_TYPE:STRING=Release\n" + "CMAKE_CXX_COMPILER:FILEPATH=/usr/bin/g++\n" + "CMAKE_CXX_FLAGS:STRING=-march=native\n" + "CMAKE_CXX_FLAGS_RELEASE:STRING=-O3 -DNDEBUG\n" + "CMAKE_EXE_LINKER_FLAGS:STRING=-Wl,--as-needed\n" + "CMAKE_EXE_LINKER_FLAGS_RELEASE:STRING=\n" + "CMAKE_GENERATOR:INTERNAL=Ninja\n" + "MPIEXEC_EXECUTABLE:FILEPATH=/usr/bin/mpiexec\n" + "MPI_CXX_COMPILER:FILEPATH=/usr/bin/mpicxx\n" + "OPTIMIZED_OUTPUT:BOOL=ON\n", + encoding="utf-8", + ) + values = parhip_harness.parse_cmake_cache(cache) + provenance = parhip_harness.build_provenance_from_cache( + values, + compiler_version="g++ 16.2.1", + mpi_version="Open MPI 5.0.10", + executable_sha256="f" * 64, + cmake_version="cmake version 4.1.0", + linked_mpi_libraries=[ + { + "name": "libmpi.so.40", + "path": "/usr/lib/libmpi.so.40", + "sha256": "e" * 64, + } + ], + ) + self.assertEqual(provenance["release_flags"], "-march=native -O3 -DNDEBUG") + self.assertEqual(provenance["optimized_output"], "ON") + self.assertEqual(provenance["executable_sha256"], "f" * 64) + self.assertEqual(provenance["link_flags"], "-Wl,--as-needed") + self.assertEqual(provenance["cmake_generator"], "Ninja") + self.assertEqual( + provenance["mpi_cache_identity"], + {"MPI_CXX_COMPILER": "/usr/bin/mpicxx"}, + ) + + +class ResultContractTests(unittest.TestCase): + def test_unavailable_collective_bytes_make_full_acceptance_incomplete( + self, + ) -> None: + # Break caught: an uninstrumented run must not be serialized as accepted. + records = [synthetic_record("baseline"), synthetic_record("candidate")] + document = parhip_harness.assemble_result_document( + records=records, + provenance={"test": True}, + bootstrap_iterations=100, + bootstrap_seed=3, + min_pairs=1, + ) + self.assertTrue(document["analysis"]["quality_performance_passed"]) + self.assertEqual( + document["analysis"]["collective_bytes"]["status"], "incomplete" + ) + self.assertFalse(document["analysis"]["collective_bytes"]["passed"]) + self.assertFalse(document["analysis"]["acceptance_complete"]) + self.assertFalse(document["analysis"]["passed"]) + json.dumps(document, allow_nan=False) + + def test_collective_record_aggregation_requires_every_rank_and_exact_totals( + self, + ) -> None: + # Break caught: launcher-level or partial-rank counters are not complete. + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + for rank, sent in enumerate((10, 30)): + operation = { + "name": "MPI_Alltoallv", + "calls": 1, + "sent_bytes": sent, + "received_bytes": sent, + "self_sent_bytes": 2, + "self_received_bytes": 2, + } + (root / f"rank-{rank}.json").write_text( + json.dumps( + { + "schema_version": 1, + "rank": rank, + "pid": 1000 + rank, + "hostname": "node-a", + "complete": True, + "error": None, + "live_persistent_requests": 0, + "operations": [operation], + "topology_setup": { + "operations": [ + { + "name": "MPI_Dist_graph_create_adjacent", + "calls": rank + 1, + "elapsed_nanoseconds": 100 + rank, + }, + { + "name": "MPI_Dist_graph_create", + "calls": 0, + "elapsed_nanoseconds": 0, + }, + ], + "totals": { + "calls": rank + 1, + "elapsed_nanoseconds": 100 + rank, + }, + }, + "totals": { + "calls": 1, + "sent_bytes": sent, + "received_bytes": sent, + "self_sent_bytes": 2, + "self_received_bytes": 2, + }, + } + ), + encoding="utf-8", + ) + result = parhip_harness.read_collective_metrics( + root, expected_ranks=2 + ) + self.assertEqual(result["global_sent_bytes"], 40) + self.assertEqual(result["global_received_bytes"], 40) + self.assertEqual(result["max_rank_endpoint_bytes"], 60) + self.assertEqual(result["global_topology_setup_calls"], 3) + self.assertEqual( + result["max_rank_topology_setup_nanoseconds"], 101 + ) + + (root / "rank-1.json").unlink() + with self.assertRaisesRegex(ValueError, "expected 2"): + parhip_harness.read_collective_metrics(root, expected_ranks=2) + + (root / "rank-1.json").write_text("{malformed\n", encoding="utf-8") + with self.assertRaises(ValueError): + parhip_harness.read_collective_metrics(root, expected_ranks=2) + + def test_complete_topology_measurements_close_the_reporting_gate(self) -> None: + # Break caught: complete external topology timing must not remain marked + # incomplete merely because ParHIP itself prints no topology timer. + records = [synthetic_record("baseline"), synthetic_record("candidate")] + for record, topology_nanoseconds in zip( + records, (0, 25_000), strict=True + ): + record["collective_bytes"] = { + "status": "complete", + "global_sent_bytes": 100, + "global_received_bytes": 100, + } + record["topology_timing"] = { + "status": "complete", + "measurement": "PMPI distributed-graph construction wall time", + "global_calls": 0 if topology_nanoseconds == 0 else 2, + "global_rank_nanoseconds": topology_nanoseconds, + "max_rank_nanoseconds": topology_nanoseconds, + } + + document = parhip_harness.assemble_result_document( + records=records, + provenance={"test": True}, + bootstrap_iterations=100, + bootstrap_seed=3, + min_pairs=1, + ) + + topology = document["analysis"]["setup_topology_timing"] + self.assertEqual(topology["status"], "complete") + self.assertTrue(topology["passed"]) + self.assertEqual( + topology["variants"]["candidate"]["max_rank_nanoseconds"], + 25_000, + ) + self.assertTrue(document["analysis"]["acceptance_complete"]) + self.assertTrue(document["analysis"]["passed"]) + + +class PmpiSourceContractTests(unittest.TestCase): + def test_interposer_covers_branch_collectives_and_persistent_lifecycle( + self, + ) -> None: + # Break caught: an interposer that counts init rather than Start inflates + # reusable persistent neighborhood exchanges. + source = ( + Path(parhip_harness.__file__).with_name("pmpi_collective_bytes.cpp") + ).read_text(encoding="utf-8") + required = { + "MPI_Alltoall", + "MPI_Alltoallv", + "MPI_Ialltoallv", + "MPI_Neighbor_alltoall", + "MPI_Neighbor_alltoallv", + "MPI_Ineighbor_alltoallv", + "MPI_Alltoallv_c", + "MPI_Ialltoallv_c", + "MPI_Neighbor_alltoallv_c", + "MPI_Ineighbor_alltoallv_c", + "MPI_Neighbor_alltoallv_init", + "MPI_Neighbor_alltoallv_init_c", + "MPI_Start", + "MPI_Startall", + "MPI_Request_free", + "MPI_Dist_graph_create", + "MPI_Dist_graph_create_adjacent", + "MPI_Finalize", + } + for symbol in required: + self.assertIn(f'extern "C" int {symbol}', source) + self.assertIn("PMPI_Type_size_x", source) + self.assertIn("charge_persistent", source) + self.assertIn("elapsed_nanoseconds", source) + self.assertIn("KAHIP_PMPI_BYTES_DIRECTORY", source) + self.assertNotIn("PMPI_Allreduce", source) + + +if __name__ == "__main__": + unittest.main() diff --git a/ci/run-limited b/ci/run-limited new file mode 100755 index 00000000..06d9a806 --- /dev/null +++ b/ci/run-limited @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -euo pipefail + +exec systemd-run --user --scope \ + -p MemoryHigh=28G \ + -p MemoryMax=30G \ + -p MemorySwapMax=2G \ + "$@" diff --git a/ci/test-verify-mpi-capabilities.cmake b/ci/test-verify-mpi-capabilities.cmake new file mode 100644 index 00000000..42624b48 --- /dev/null +++ b/ci/test-verify-mpi-capabilities.cmake @@ -0,0 +1,80 @@ +cmake_minimum_required(VERSION 4.0) + +if(NOT DEFINED SOURCE_DIR) + get_filename_component(SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE) +endif() + +set(verifier "${SOURCE_DIR}/ci/verify-mpi-capabilities.cmake") +if(NOT EXISTS "${verifier}") + message(FATAL_ERROR "MPI capability verifier is missing: ${verifier}") +endif() + +if(NOT DEFINED TEST_ROOT) + string(RANDOM LENGTH 12 ALPHABET 0123456789abcdef test_suffix) + set(TEST_ROOT "/tmp/kahip-mpi-capabilities-${test_suffix}") +endif() + +function(write_fixture fixture bcast_c alltoallv_c allreduce_c reduce_c + neighbor_alltoallv_c ineighbor_alltoallv ineighbor_alltoallv_c + neighbor_alltoallv_init neighbor_alltoallv_init_c) + set(generated_dir + "${TEST_ROOT}/${fixture}/parallel/parallel_src/generated") + file(MAKE_DIRECTORY "${generated_dir}") + file(WRITE "${generated_dir}/kahip_mpi_capabilities.h" + "#define KAHIP_HAVE_MPI_ALLTOALLV_C ${alltoallv_c}\n" + "#define KAHIP_HAVE_MPI_ALLREDUCE_C ${allreduce_c}\n" + "#define KAHIP_HAVE_MPI_REDUCE_C ${reduce_c}\n" + "#define KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C ${neighbor_alltoallv_c}\n" + "#define KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV ${ineighbor_alltoallv}\n" + "#define KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV_C ${ineighbor_alltoallv_c}\n" + "#define KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT ${neighbor_alltoallv_init}\n" + "#define KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT_C ${neighbor_alltoallv_init_c}\n") + file(WRITE "${TEST_ROOT}/${fixture}/CMakeCache.txt" + "KAHIP_HAVE_MPI_BCAST_C:INTERNAL=${bcast_c}\n") +endfunction() + +function(expect_profile fixture profile should_pass) + execute_process( + COMMAND "${CMAKE_COMMAND}" + "-DBUILD_DIR=${TEST_ROOT}/${fixture}" + "-DPROFILE=${profile}" + -P "${verifier}" + RESULT_VARIABLE result + OUTPUT_VARIABLE output + ERROR_VARIABLE error) + + if(should_pass AND NOT result EQUAL 0) + message(FATAL_ERROR + "${profile} unexpectedly rejected ${fixture}:\n${output}${error}") + elseif(NOT should_pass AND result EQUAL 0) + message(FATAL_ERROR + "${profile} unexpectedly accepted ${fixture}:\n${output}${error}") + endif() +endfunction() + +write_fixture(mpi3 "" 0 0 0 0 1 0 0 0) +expect_profile(mpi3 mpi3-floor TRUE) + +write_fixture(mpi3_with_legacy_persistent "" 0 0 0 0 1 0 1 0) +expect_profile(mpi3_with_legacy_persistent mpi3-floor TRUE) + +write_fixture(mpi3_with_large_count 1 1 0 0 0 1 0 0 0) +expect_profile(mpi3_with_large_count mpi3-floor FALSE) + +write_fixture(mpi3_with_reduction_large_count "" 0 1 1 0 1 0 0 0) +expect_profile(mpi3_with_reduction_large_count mpi3-floor FALSE) + +write_fixture(mpi4 1 1 1 1 1 1 1 1 1) +expect_profile(mpi4 mpi4 TRUE) + +write_fixture(mpi4_without_allreduce_c 1 1 0 1 1 1 1 1 1) +expect_profile(mpi4_without_allreduce_c mpi4 FALSE) + +write_fixture(mpi4_without_reduce_c 1 1 1 0 1 1 1 1 1) +expect_profile(mpi4_without_reduce_c mpi4 FALSE) + +write_fixture(mpi4_without_persistent_c 1 1 1 1 1 1 1 1 0) +expect_profile(mpi4_without_persistent_c mpi4 FALSE) + +file(REMOVE_RECURSE "${TEST_ROOT}") +message(STATUS "MPI capability profile verifier self-test passed") diff --git a/ci/verify-mpi-capabilities.cmake b/ci/verify-mpi-capabilities.cmake new file mode 100644 index 00000000..fd889ef6 --- /dev/null +++ b/ci/verify-mpi-capabilities.cmake @@ -0,0 +1,101 @@ +cmake_minimum_required(VERSION 4.0) + +if(NOT DEFINED BUILD_DIR OR BUILD_DIR STREQUAL "") + message(FATAL_ERROR "BUILD_DIR is required") +endif() +if(NOT PROFILE MATCHES "^(mpi3-floor|mpi4)$") + message(FATAL_ERROR "PROFILE must be mpi3-floor or mpi4") +endif() + +cmake_path(ABSOLUTE_PATH BUILD_DIR NORMALIZE OUTPUT_VARIABLE build_dir) +set(capability_header + "${build_dir}/parallel/parallel_src/generated/kahip_mpi_capabilities.h") +set(cache_file "${build_dir}/CMakeCache.txt") + +if(NOT EXISTS "${capability_header}") + message(FATAL_ERROR "Missing generated MPI capability header: ${capability_header}") +endif() +if(NOT EXISTS "${cache_file}") + message(FATAL_ERROR "Missing CMake cache: ${cache_file}") +endif() + +function(read_header_capability name output) + file(STRINGS "${capability_header}" definition + REGEX "^#define ${name} [01]$") + list(LENGTH definition definition_count) + if(NOT definition_count EQUAL 1) + message(FATAL_ERROR + "Expected exactly one 0/1 definition for ${name} in ${capability_header}") + endif() + string(REGEX REPLACE "^#define ${name} ([01])$" "\\1" value "${definition}") + set("${output}" "${value}" PARENT_SCOPE) +endfunction() + +function(read_cache_boolean name output) + file(STRINGS "${cache_file}" cache_entry REGEX "^${name}:[^=]*=") + list(LENGTH cache_entry entry_count) + if(NOT entry_count EQUAL 1) + message(FATAL_ERROR + "Expected exactly one cache entry for ${name} in ${cache_file}") + endif() + string(REGEX REPLACE "^[^=]*=" "" raw_value "${cache_entry}") + string(TOUPPER "${raw_value}" normalized_value) + if(normalized_value MATCHES "^(1|ON|TRUE|YES|Y)$") + set(value 1) + elseif(normalized_value MATCHES "^(|0|OFF|FALSE|NO|N|IGNORE|NOTFOUND)$" + OR normalized_value MATCHES "-NOTFOUND$") + set(value 0) + else() + message(FATAL_ERROR + "${name} has non-boolean cache value '${raw_value}' in ${cache_file}") + endif() + set("${output}" "${value}" PARENT_SCOPE) +endfunction() + +set(header_capabilities + KAHIP_HAVE_MPI_ALLTOALLV_C + KAHIP_HAVE_MPI_ALLREDUCE_C + KAHIP_HAVE_MPI_REDUCE_C + KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C + KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV + KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV_C + KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT + KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT_C) + +foreach(capability IN LISTS header_capabilities) + read_header_capability("${capability}" "${capability}") +endforeach() +read_cache_boolean(KAHIP_HAVE_MPI_BCAST_C KAHIP_HAVE_MPI_BCAST_C) + +if(PROFILE STREQUAL "mpi3-floor") + set(expected_KAHIP_HAVE_MPI_BCAST_C 0) + set(expected_KAHIP_HAVE_MPI_ALLTOALLV_C 0) + set(expected_KAHIP_HAVE_MPI_ALLREDUCE_C 0) + set(expected_KAHIP_HAVE_MPI_REDUCE_C 0) + set(expected_KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C 0) + set(expected_KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV 1) + set(expected_KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV_C 0) + set(expected_KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT_C 0) +else() + set(expected_KAHIP_HAVE_MPI_BCAST_C 1) + foreach(capability IN LISTS header_capabilities) + set("expected_${capability}" 1) + endforeach() +endif() + +set(all_capabilities KAHIP_HAVE_MPI_BCAST_C ${header_capabilities}) +foreach(capability IN LISTS all_capabilities) + set(expected_variable "expected_${capability}") + if(NOT DEFINED "${expected_variable}") + message(STATUS "${capability}=${${capability}} (optional for ${PROFILE})") + continue() + endif() + if(NOT "${${capability}}" STREQUAL "${${expected_variable}}") + message(FATAL_ERROR + "${PROFILE} requires ${capability}=${${expected_variable}}, " + "but ${build_dir} detected ${${capability}}") + endif() + message(STATUS "${capability}=${${capability}}") +endforeach() + +message(STATUS "MPI capability profile '${PROFILE}' verified") diff --git a/cmake/Cache.cmake b/cmake/Cache.cmake new file mode 100644 index 00000000..1e7e1658 --- /dev/null +++ b/cmake/Cache.cmake @@ -0,0 +1,34 @@ +# Enable cache if available +function(kahip_enable_cache) + set(CACHE_OPTION "ccache" CACHE STRING "Compiler cache to be used") + set(CACHE_OPTION_VALUES "ccache" "sccache") + set_property(CACHE CACHE_OPTION PROPERTY STRINGS ${CACHE_OPTION_VALUES}) + list(FIND CACHE_OPTION_VALUES ${CACHE_OPTION} CACHE_OPTION_INDEX) + + if(${CACHE_OPTION_INDEX} EQUAL -1) + message( + STATUS + "Using custom compiler cache system: '${CACHE_OPTION}', explicitly supported entries are ${CACHE_OPTION_VALUES}" + ) + endif() + + find_program(CACHE_BINARY NAMES ${CACHE_OPTION_VALUES}) + if(CACHE_BINARY) + message(STATUS "${CACHE_BINARY} found and enabled") + set(CMAKE_CXX_COMPILER_LAUNCHER + ${CACHE_BINARY} + CACHE FILEPATH + "CXX compiler cache used" + ) + set(CMAKE_C_COMPILER_LAUNCHER + ${CACHE_BINARY} + CACHE FILEPATH + "C compiler cache used" + ) + else() + message( + WARNING + "${CACHE_OPTION} is enabled but was not found. Not using it" + ) + endif() +endfunction() diff --git a/cmake/CompilerWarnings.cmake b/cmake/CompilerWarnings.cmake new file mode 100644 index 00000000..36c12d36 --- /dev/null +++ b/cmake/CompilerWarnings.cmake @@ -0,0 +1,122 @@ +# from here: +# +# https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Available.md + +function( + kahip_set_project_warnings + project_name + WARNINGS_AS_ERRORS + MSVC_WARNINGS + CLANG_WARNINGS + GCC_WARNINGS + CUDA_WARNINGS +) + if("${MSVC_WARNINGS}" STREQUAL "") + set(MSVC_WARNINGS + /W4 # Baseline reasonable warnings + /w14242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data + /w14254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data + /w14263 # 'function': member function does not override any base class virtual member function + /w14265 # 'classname': class has virtual functions, but destructor is not virtual instances of this class may not + # be destructed correctly + /w14287 # 'operator': unsigned/negative constant mismatch + /we4289 # nonstandard extension used: 'variable': loop control variable declared in the for-loop is used outside + # the for-loop scope + /w14296 # 'operator': expression is always 'boolean_value' + /w14311 # 'variable': pointer truncation from 'type1' to 'type2' + /w14545 # expression before comma evaluates to a function which is missing an argument list + /w14546 # function call before comma missing argument list + /w14547 # 'operator': operator before comma has no effect; expected operator with side-effect + /w14549 # 'operator': operator before comma has no effect; did you intend 'operator'? + /w14555 # expression has no effect; expected expression with side- effect + /w14619 # pragma warning: there is no warning number 'number' + /w14640 # Enable warning on thread un-safe static member initialization + /w14826 # Conversion from 'type1' to 'type2' is sign-extended. This may cause unexpected runtime behavior. + /w14905 # wide string literal cast to 'LPSTR' + /w14906 # string literal cast to 'LPWSTR' + /w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied + /permissive- # standards conformance mode for MSVC compiler. + ) + endif() + + if("${CLANG_WARNINGS}" STREQUAL "") + set(CLANG_WARNINGS + -Wall + -Wextra # reasonable and standard + -Wshadow # warn the user if a variable declaration shadows one from a parent context + -Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps + # catch hard to track down memory errors + -Wold-style-cast # warn for c-style casts + -Wcast-align # warn for potential performance problem casts + -Wunused # warn on anything being unused + -Woverloaded-virtual # warn if you overload (not override) a virtual function + -Wpedantic # warn if non-standard C++ is used + -Wconversion # warn on type conversions that may lose data + -Wno-sign-conversion # warn on sign conversions + -Wnull-dereference # warn if a null dereference is detected + -Wdouble-promotion # warn if float is implicit promoted to double + -Wformat=2 # warn on security issues around functions that format output (ie printf) + -Wimplicit-fallthrough # warn on statements that fallthrough without an explicit annotation + -Wno-float-conversion + ) + endif() + + if("${GCC_WARNINGS}" STREQUAL "") + set(GCC_WARNINGS + ${CLANG_WARNINGS} + -Wmisleading-indentation # warn if indentation implies blocks where blocks do not exist + -Wduplicated-cond # warn if if / else chain has duplicated conditions + -Wduplicated-branches # warn if if / else branches have duplicated code + -Wlogical-op # warn about logical operations being used where bitwise were probably wanted + -Wuseless-cast # warn if you perform a cast to the same type + -Wsuggest-override # warn if an overridden member function is not marked 'override' or 'final' + ) + endif() + + if("${CUDA_WARNINGS}" STREQUAL "") + set(CUDA_WARNINGS + -Wall + -Wextra + -Wunused + -Wconversion + -Wshadow + # TODO add more Cuda warnings + ) + endif() + + if(WARNINGS_AS_ERRORS) + message(TRACE "Warnings are treated as errors") + list(APPEND CLANG_WARNINGS -Werror) + list(APPEND GCC_WARNINGS -Werror) + list(APPEND MSVC_WARNINGS /WX) + endif() + + if(MSVC) + set(PROJECT_WARNINGS_CXX ${MSVC_WARNINGS}) + elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + set(PROJECT_WARNINGS_CXX ${CLANG_WARNINGS}) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(PROJECT_WARNINGS_CXX ${GCC_WARNINGS}) + else() + message( + AUTHOR_WARNING + "No compiler warnings set for CXX compiler: '${CMAKE_CXX_COMPILER_ID}'" + ) + # TODO support Intel compiler + endif() + + # use the same warning flags for C + set(PROJECT_WARNINGS_C "${PROJECT_WARNINGS_CXX}") + + set(PROJECT_WARNINGS_CUDA "${CUDA_WARNINGS}") + + target_compile_options( + ${project_name} + INTERFACE # C++ warnings + $<$,$>:$<$:${PROJECT_WARNINGS_CXX}>> + # C warnings + $<$,$>:$<$:${PROJECT_WARNINGS_C}>> + # Cuda warnings + $<$,$>:$<$:${PROJECT_WARNINGS_CUDA}>> + ) +endfunction() diff --git a/cmake/FindGurobi.cmake b/cmake/FindGurobi.cmake index 79b48c92..768d6dc0 100644 --- a/cmake/FindGurobi.cmake +++ b/cmake/FindGurobi.cmake @@ -6,35 +6,29 @@ # GUROBI_INCLUDE_DIRS - The Gurobi include directories # GUROBI_LIBRARIES - The libraries needed to use Gurobi -find_path(GUROBI_INCLUDE_DIR - NAMES gurobi_c++.h - PATHS "$ENV{GUROBI_HOME}/include" - ) - -find_library( GUROBI_LIBRARY - NAMES gurobi90 - gurobi81 - gurobi80 - gurobi75 - PATHS "$ENV{GUROBI_HOME}/lib" - ) +find_path( + GUROBI_INCLUDE_DIR + NAMES gurobi_c++.h + PATHS "$ENV{GUROBI_HOME}/include" +) +find_library( + GUROBI_LIBRARY + NAMES gurobi90 gurobi81 gurobi80 gurobi75 + PATHS "$ENV{GUROBI_HOME}/lib" +) -find_library( GUROBI_CXX_LIBRARY - NAMES gurobi_g++5.2 - PATHS "$ENV{GUROBI_HOME}/lib" - ) +find_library( + GUROBI_CXX_LIBRARY + NAMES gurobi_g++5.2 + PATHS "$ENV{GUROBI_HOME}/lib" +) set(GUROBI_INCLUDE_DIRS "${GUROBI_INCLUDE_DIR}") set(GUROBI_LIBRARIES "${GUROBI_CXX_LIBRARY};${GUROBI_LIBRARY}") -if (GUROBI_INCLUDE_DIRS AND GUROBI_LIBRARIES) - set(GUROBI_FOUND TRUE) +if(GUROBI_INCLUDE_DIRS AND GUROBI_LIBRARIES) + set(GUROBI_FOUND TRUE) endif() -mark_as_advanced( - GUROBI_INCLUDE_DIRS - GUROBI_LIBRARIES -) - - +mark_as_advanced(GUROBI_INCLUDE_DIRS GUROBI_LIBRARIES) diff --git a/cmake/InterproceduralOptimization.cmake b/cmake/InterproceduralOptimization.cmake new file mode 100644 index 00000000..9dfdd553 --- /dev/null +++ b/cmake/InterproceduralOptimization.cmake @@ -0,0 +1,9 @@ +function(kahip_enable_ipo) + include(CheckIPOSupported) + check_ipo_supported(RESULT result OUTPUT output) + if(result) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) + else() + message(WARNING "IPO is not supported: ${output}") + endif() +endfunction() diff --git a/cmake/KaHIPSettings.cmake b/cmake/KaHIPSettings.cmake new file mode 100644 index 00000000..e70de1f3 --- /dev/null +++ b/cmake/KaHIPSettings.cmake @@ -0,0 +1,120 @@ +message(STATUS "Configuring Kahip") + +include("${CMAKE_CURRENT_LIST_DIR}/Cache.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/CompilerWarnings.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/Sanitizers.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/StaticAnalyzers.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/Utilities.cmake") + +kahip_supports_sanitizers() + +option(kahip_ENABLE_IPO "Enable IPO/LTO" ON) +option(kahip_ENABLE_UNITY_BUILD "Enable Unity Build Mode" OFF) +option(kahip_ENABLE_USER_LINKER "Enable user-selected linker" OFF) +option(kahip_WARNINGS_AS_ERRORS "Treat Warnings As Errors" OFF) +option(kahip_ENABLE_SANITIZERS "Enable sanitizers" OFF) +option( + kahip_ENABLE_SANITIZER_ADDRESS + "Enable address sanitizer" + ${SUPPORTS_ASAN} +) +option(kahip_ENABLE_SANITIZER_LEAK "Enable leak sanitizer" OFF) +option( + kahip_ENABLE_SANITIZER_UNDEFINED + "Enable undefined sanitizer" + ${SUPPORTS_UBSAN} +) +option(kahip_ENABLE_SANITIZER_THREAD "Enable thread sanitizer" OFF) +option(kahip_ENABLE_SANITIZER_MEMORY "Enable memory sanitizer" OFF) +option(kahip_ENABLE_CLANG_TIDY "Enable clang-tidy" OFF) +option(kahip_ENABLE_CPPCHECK "Enable cpp-check analysis" OFF) +option(kahip_ENABLE_IWYU "Enable `include_what_you_use`" OFF) +option(kahip_ENABLE_CACHE "Enable ccache" OFF) +option(NONATIVEOPTIMIZATIONS "Disable --march=native optimizations" OFF) + +if(kahip_ENABLE_UNITY_BUILD) + set(CMAKE_UNITY_BUILD ON) +endif() + +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH ON) + +# KaHIP does not use C++ modules. Avoid requiring a separate compiler +# dependency scanner merely because the project selects C++23. +set(CMAKE_CXX_SCAN_FOR_MODULES OFF) + +add_library(kahip_warnings INTERFACE) +add_library(kahip_options INTERFACE) + +if(kahip_ENABLE_IPO) + if(CMAKE_CXX_COMPILER_ID MATCHES "^Cray") + message(VERBOSE "IPO: Cray compiler found") + target_compile_options(kahip_options INTERFACE -flto) + target_link_options(kahip_options INTERFACE -flto) + else () + include("${CMAKE_CURRENT_LIST_DIR}/InterproceduralOptimization.cmake") + kahip_enable_ipo() + endif () +endif() + +include("${CMAKE_CURRENT_LIST_DIR}/StandardProjectSettings.cmake") + +# tweak compiler flags +target_compile_features(kahip_options INTERFACE cxx_std_23) +message(VERBOSE "Checking compiler feature support") +check_cxx_compiler_flag(-funroll-loops COMPILER_SUPPORTS_FUNROLL_LOOPS) +target_compile_options( + kahip_options + INTERFACE $<$:-funroll-loops> +) +check_cxx_compiler_flag(-fno-stack-limit COMPILER_SUPPORTS_FNOSTACKLIMITS) +target_compile_options( + kahip_options + INTERFACE + $<$:-fno-stack-limit> +) +check_cxx_compiler_flag(-march=native COMPILER_SUPPORTS_MARCH_NATIVE) +target_compile_options( + kahip_options + INTERFACE + $<$,$>,$,$>>:-march=native> +) + +if(kahip_ENABLE_USER_LINKER) + include("${CMAKE_CURRENT_LIST_DIR}/Linker.cmake") + kahip_configure_linker(kahip_options) +endif() + +kahip_set_project_warnings( + kahip_warnings + ${kahip_WARNINGS_AS_ERRORS} + "" + "" + "" + "" +) + +if(kahip_ENABLE_SANITIZERS) + kahip_enable_sanitizers( + kahip_options + ${kahip_ENABLE_SANITIZER_ADDRESS} + ${kahip_ENABLE_SANITIZER_LEAK} + ${kahip_ENABLE_SANITIZER_UNDEFINED} + ${kahip_ENABLE_SANITIZER_THREAD} + ${kahip_ENABLE_SANITIZER_MEMORY} + ) +endif() + +if(kahip_ENABLE_CLANG_TIDY) + kahip_enable_clang_tidy(kahip_options ${kahip_WARNINGS_AS_ERRORS}) +endif() + +if(kahip_ENABLE_CPPCHECK) + kahip_enable_cppcheck( + ${kahip_WARNINGS_AS_ERRORS} + "" # override cppcheck options + ) +endif() + +if(kahip_ENABLE_IWYU) + kahip_enable_include_what_you_use() +endif() diff --git a/cmake/KahipInstallConsumerArguments.cmake b/cmake/KahipInstallConsumerArguments.cmake new file mode 100644 index 00000000..7703fff0 --- /dev/null +++ b/cmake/KahipInstallConsumerArguments.cmake @@ -0,0 +1,8 @@ +include_guard(GLOBAL) + +function(kahip_append_consumer_cache_argument command_variable name value) + string(REPLACE ";" "\\;" escaped_value "${value}") + set(command "${${command_variable}}") + list(APPEND command "-D${name}=${escaped_value}") + set(${command_variable} "${command}" PARENT_SCOPE) +endfunction() diff --git a/cmake/KahipPkgConfig.cmake b/cmake/KahipPkgConfig.cmake new file mode 100644 index 00000000..9cc93a5f --- /dev/null +++ b/cmake/KahipPkgConfig.cmake @@ -0,0 +1,105 @@ +include_guard(GLOBAL) + +function(_kahip_pkg_config_escape output_variable value) + if(value MATCHES "[\r\n]") + message(FATAL_ERROR "pkg-config flags cannot contain newlines") + endif() + + string(REPLACE "\\" "\\\\" escaped_value "${value}") + string(REPLACE " " "\\ " escaped_value "${escaped_value}") + set(${output_variable} "${escaped_value}" PARENT_SCOPE) +endfunction() + +function(_kahip_join_pkg_config_flags output_variable prefix) + set(rendered_flags "") + foreach(flag IN LISTS ARGN) + _kahip_pkg_config_escape(escaped_flag "${flag}") + list(APPEND rendered_flags "${prefix}${escaped_flag}") + endforeach() + string(JOIN " " joined_flags ${rendered_flags}) + set(${output_variable} "${joined_flags}" PARENT_SCOPE) +endfunction() + +function(kahip_format_serial_api_pkg_config_cflags output_variable) + cmake_parse_arguments(PARSE_ARGV 1 api "64BIT;METIS" "" "") + if(api_UNPARSED_ARGUMENTS) + message( + FATAL_ERROR + "unrecognized serial API pkg-config arguments: ${api_UNPARSED_ARGUMENTS}" + ) + endif() + + set(public_definitions "") + if(api_64BIT) + list(APPEND public_definitions KAHIP_64BIT) + endif() + if(api_METIS) + list(APPEND public_definitions USEMETIS) + endif() + _kahip_join_pkg_config_flags( + rendered_definitions + -D + ${public_definitions} + ) + set(${output_variable} "${rendered_definitions}" PARENT_SCOPE) +endfunction() + +function(kahip_format_mpi_pkg_config_flags cflags_output libs_output) + cmake_parse_arguments( + PARSE_ARGV + 2 + mpi + "" + "" + "INCLUDE_DIRECTORIES;COMPILE_DEFINITIONS;COMPILE_OPTIONS;LINK_OPTIONS;LIBRARIES" + ) + if(mpi_UNPARSED_ARGUMENTS) + message( + FATAL_ERROR + "unrecognized MPI pkg-config arguments: ${mpi_UNPARSED_ARGUMENTS}" + ) + endif() + + _kahip_join_pkg_config_flags( + include_flags + -I + ${mpi_INCLUDE_DIRECTORIES} + ) + _kahip_join_pkg_config_flags( + definition_flags + -D + ${mpi_COMPILE_DEFINITIONS} + ) + _kahip_join_pkg_config_flags( + compile_option_flags + "" + ${mpi_COMPILE_OPTIONS} + ) + + set(cflags ${include_flags} ${definition_flags} ${compile_option_flags}) + list(FILTER cflags EXCLUDE REGEX "^$") + string(JOIN " " cflags ${cflags}) + + _kahip_join_pkg_config_flags( + link_option_flags + "" + ${mpi_LINK_OPTIONS} + ) + set(library_flags "") + foreach(library IN LISTS mpi_LIBRARIES) + if(IS_ABSOLUTE "${library}" OR library MATCHES "^-") + set(library_flag "${library}") + else() + set(library_flag "-l${library}") + endif() + _kahip_pkg_config_escape(escaped_library "${library_flag}") + list(APPEND library_flags "${escaped_library}") + endforeach() + + set(libs ${link_option_flags} ${library_flags}) + list(FILTER libs EXCLUDE REGEX "^$") + string(JOIN " " libs ${libs}) + + set(${cflags_output} "${cflags}" PARENT_SCOPE) + set(${libs_output} "${libs}" PARENT_SCOPE) +endfunction() diff --git a/cmake/Linker.cmake b/cmake/Linker.cmake new file mode 100644 index 00000000..94c78042 --- /dev/null +++ b/cmake/Linker.cmake @@ -0,0 +1,34 @@ +macro(kahip_configure_linker project_name) + include(CheckCXXCompilerFlag) + + set(USER_LINKER_OPTION "lld" CACHE STRING "Linker to be used") + set(USER_LINKER_OPTION_VALUES "lld" "gold" "bfd" "mold") + set_property( + CACHE USER_LINKER_OPTION + PROPERTY STRINGS ${USER_LINKER_OPTION_VALUES} + ) + list( + FIND + USER_LINKER_OPTION_VALUES + ${USER_LINKER_OPTION} + USER_LINKER_OPTION_INDEX + ) + + if(${USER_LINKER_OPTION_INDEX} EQUAL -1) + message( + STATUS + "Using custom linker: '${USER_LINKER_OPTION}', explicitly supported entries are ${USER_LINKER_OPTION_VALUES}" + ) + endif() + + if(NOT myproject_ENABLE_USER_LINKER) + return() + endif() + + set(LINKER_FLAG "-fuse-ld=${USER_LINKER_OPTION}") + + check_cxx_compiler_flag(${LINKER_FLAG} CXX_SUPPORTS_USER_LINKER) + if(CXX_SUPPORTS_USER_LINKER) + target_compile_options(${project_name} INTERFACE ${LINKER_FLAG}) + endif() +endmacro() diff --git a/cmake/Sanitizers.cmake b/cmake/Sanitizers.cmake new file mode 100644 index 00000000..f2f80786 --- /dev/null +++ b/cmake/Sanitizers.cmake @@ -0,0 +1,143 @@ +macro(kahip_supports_sanitizers) + if( + ( + CMAKE_CXX_COMPILER_ID MATCHES ".*Clang.*" + OR CMAKE_CXX_COMPILER_ID MATCHES ".*GNU.*" + ) + AND NOT WIN32 + ) + set(SUPPORTS_UBSAN ON) + else() + set(SUPPORTS_UBSAN OFF) + endif() + + if( + ( + CMAKE_CXX_COMPILER_ID MATCHES ".*Clang.*" + OR CMAKE_CXX_COMPILER_ID MATCHES ".*GNU.*" + ) + AND WIN32 + ) + set(SUPPORTS_ASAN OFF) + else() + set(SUPPORTS_ASAN ON) + endif() +endmacro() + +function( + kahip_enable_sanitizers + project_name + ENABLE_SANITIZER_ADDRESS + ENABLE_SANITIZER_LEAK + ENABLE_SANITIZER_UNDEFINED_BEHAVIOR + ENABLE_SANITIZER_THREAD + ENABLE_SANITIZER_MEMORY +) + if( + CMAKE_CXX_COMPILER_ID STREQUAL "GNU" + OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang" + ) + set(SANITIZERS "") + + if(${ENABLE_SANITIZER_ADDRESS}) + list(APPEND SANITIZERS "address") + endif() + + if(${ENABLE_SANITIZER_LEAK}) + list(APPEND SANITIZERS "leak") + endif() + + if(${ENABLE_SANITIZER_UNDEFINED_BEHAVIOR}) + list(APPEND SANITIZERS "undefined") + endif() + + if(${ENABLE_SANITIZER_THREAD}) + if("address" IN_LIST SANITIZERS OR "leak" IN_LIST SANITIZERS) + message( + WARNING + "Thread sanitizer does not work with Address and Leak sanitizer enabled" + ) + else() + list(APPEND SANITIZERS "thread") + endif() + endif() + + if( + ${ENABLE_SANITIZER_MEMORY} + AND CMAKE_CXX_COMPILER_ID MATCHES ".*Clang" + ) + message( + WARNING + "Memory sanitizer requires all the code (including libc++) to be MSan-instrumented otherwise it reports false positives" + ) + if( + "address" IN_LIST SANITIZERS + OR "thread" IN_LIST SANITIZERS + OR "leak" IN_LIST SANITIZERS + ) + message( + WARNING + "Memory sanitizer does not work with Address, Thread or Leak sanitizer enabled" + ) + else() + list(APPEND SANITIZERS "memory") + endif() + endif() + elseif(MSVC) + if(${ENABLE_SANITIZER_ADDRESS}) + list(APPEND SANITIZERS "address") + endif() + if( + ${ENABLE_SANITIZER_LEAK} + OR ${ENABLE_SANITIZER_UNDEFINED_BEHAVIOR} + OR ${ENABLE_SANITIZER_THREAD} + OR ${ENABLE_SANITIZER_MEMORY} + ) + message(WARNING "MSVC only supports address sanitizer") + endif() + endif() + + list(JOIN SANITIZERS "," LIST_OF_SANITIZERS) + + if(LIST_OF_SANITIZERS) + if(NOT "${LIST_OF_SANITIZERS}" STREQUAL "") + if(NOT MSVC) + target_compile_options( + ${project_name} + INTERFACE -fsanitize=${LIST_OF_SANITIZERS} + ) + target_link_options( + ${project_name} + INTERFACE -fsanitize=${LIST_OF_SANITIZERS} + ) + else() + string( + FIND + "$ENV{PATH}" + "$ENV{VSINSTALLDIR}" + index_of_vs_install_dir + ) + if("${index_of_vs_install_dir}" STREQUAL "-1") + message( + SEND_ERROR + "Using MSVC sanitizers requires setting the MSVC environment before building the project. Please manually open the MSVC command prompt and rebuild the project." + ) + endif() + target_compile_options( + ${project_name} + INTERFACE + /fsanitize=${LIST_OF_SANITIZERS} + /Zi + /INCREMENTAL:NO + ) + target_compile_definitions( + ${project_name} + INTERFACE + _DISABLE_VECTOR_ANNOTATION + _DISABLE_STRING_ANNOTATION + ) + target_link_options(${project_name} INTERFACE /INCREMENTAL:NO) + endif() + endif() + endif() +endfunction() diff --git a/cmake/StandardProjectSettings.cmake b/cmake/StandardProjectSettings.cmake new file mode 100644 index 00000000..38638e01 --- /dev/null +++ b/cmake/StandardProjectSettings.cmake @@ -0,0 +1,54 @@ +# Set a default build type if none was specified +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message( + STATUS + "Setting build type to 'RelWithDebInfo' as none was specified." + ) + set(CMAKE_BUILD_TYPE + RelWithDebInfo + CACHE STRING + "Choose the type of build." + FORCE + ) + # Set the possible values of build type for cmake-gui, ccmake + set_property( + CACHE CMAKE_BUILD_TYPE + PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo" + ) +endif() + +# Generate compile_commands.json to make it easier to work with clang based tools +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +# Enhance error reporting and compiler messages +if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + if(WIN32) + # On Windows cuda nvcc uses cl and not clang + add_compile_options( + $<$:-fcolor-diagnostics> + $<$:-fcolor-diagnostics> + ) + else() + add_compile_options(-fcolor-diagnostics) + endif() +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + if(WIN32) + # On Windows cuda nvcc uses cl and not gcc + add_compile_options( + $<$:-fdiagnostics-color=always> + $<$:-fdiagnostics-color=always> + ) + else() + add_compile_options(-fdiagnostics-color=always) + endif() +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND MSVC_VERSION GREATER 1900) + add_compile_options(/diagnostics:column) +else() + message( + STATUS + "No colored compiler diagnostic set for '${CMAKE_CXX_COMPILER_ID}' compiler." + ) +endif() diff --git a/cmake/StaticAnalyzers.cmake b/cmake/StaticAnalyzers.cmake new file mode 100644 index 00000000..a1b5d17e --- /dev/null +++ b/cmake/StaticAnalyzers.cmake @@ -0,0 +1,129 @@ +macro(kahip_enable_cppcheck WARNINGS_AS_ERRORS CPPCHECK_OPTIONS) + find_program(CPPCHECK cppcheck) + if(CPPCHECK) + if(CMAKE_GENERATOR MATCHES ".*Visual Studio.*") + set(CPPCHECK_TEMPLATE "vs") + else() + set(CPPCHECK_TEMPLATE "gcc") + endif() + + if("${CPPCHECK_OPTIONS}" STREQUAL "") + # Enable all warnings that are actionable by the user of this toolset + # style should enable the other 3, but we'll be explicit just in case + set(SUPPRESS_DIR "*:${CMAKE_CURRENT_BINARY_DIR}/_deps/*.h") + message(STATUS "CPPCHECK_OPTIONS suppress: ${SUPPRESS_DIR}") + set(CMAKE_CXX_CPPCHECK + ${CPPCHECK} + --template=${CPPCHECK_TEMPLATE} + --enable=style,performance,warning,portability + --inline-suppr + # We cannot act on a bug/missing feature of cppcheck + --suppress=cppcheckError + --suppress=internalAstError + # if a file does not have an internalAstError, we get an unmatchedSuppression error + --suppress=unmatchedSuppression + # noisy and incorrect sometimes + --suppress=passedByValue + # ignores code that cppcheck thinks is invalid C++ + --suppress=syntaxError + --suppress=preprocessorErrorDirective + --inconclusive + --suppress=${SUPPRESS_DIR} + ) + else() + # if the user provides a CPPCHECK_OPTIONS with a template specified, it will override this template + set(CMAKE_CXX_CPPCHECK + ${CPPCHECK} + --template=${CPPCHECK_TEMPLATE} + ${CPPCHECK_OPTIONS} + ) + endif() + + if(NOT "${CMAKE_CXX_STANDARD}" STREQUAL "") + set(CMAKE_CXX_CPPCHECK + ${CMAKE_CXX_CPPCHECK} + --std=c++${CMAKE_CXX_STANDARD} + ) + endif() + if(${WARNINGS_AS_ERRORS}) + list(APPEND CMAKE_CXX_CPPCHECK --error-exitcode=2) + endif() + else() + message( + ${WARNING_MESSAGE} + "cppcheck requested but executable not found" + ) + endif() +endmacro() + +macro(kahip_enable_clang_tidy target WARNINGS_AS_ERRORS) + find_program(CLANGTIDY clang-tidy) + if(CLANGTIDY) + if(NOT CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + get_target_property( + TARGET_PCH + ${target} + INTERFACE_PRECOMPILE_HEADERS + ) + + if("${TARGET_PCH}" STREQUAL "TARGET_PCH-NOTFOUND") + get_target_property(TARGET_PCH ${target} PRECOMPILE_HEADERS) + endif() + + if(NOT ("${TARGET_PCH}" STREQUAL "TARGET_PCH-NOTFOUND")) + message( + SEND_ERROR + "clang-tidy cannot be enabled with non-clang compiler and PCH, clang-tidy fails to handle gcc's PCH file" + ) + endif() + endif() + + # construct the clang-tidy command line + set(CLANG_TIDY_OPTIONS + ${CLANGTIDY} + -extra-arg=-Wno-unknown-warning-option + -extra-arg=-Wno-ignored-optimization-argument + -extra-arg=-Wno-unused-command-line-argument + -p + ) + # set standard + if(NOT "${CMAKE_CXX_STANDARD}" STREQUAL "") + if("${CLANG_TIDY_OPTIONS_DRIVER_MODE}" STREQUAL "cl") + set(CLANG_TIDY_OPTIONS + ${CLANG_TIDY_OPTIONS} + -extra-arg=/std:c++${CMAKE_CXX_STANDARD} + ) + else() + set(CLANG_TIDY_OPTIONS + ${CLANG_TIDY_OPTIONS} + -extra-arg=-std=c++${CMAKE_CXX_STANDARD} + ) + endif() + endif() + + # set warnings as errors + if(${WARNINGS_AS_ERRORS}) + list(APPEND CLANG_TIDY_OPTIONS -warnings-as-errors=*) + endif() + + message("Also setting clang-tidy globally") + set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY_OPTIONS}) + else() + message( + ${WARNING_MESSAGE} + "clang-tidy requested but executable not found" + ) + endif() +endmacro() + +macro(kahip_enable_include_what_you_use) + find_program(INCLUDE_WHAT_YOU_USE include-what-you-use) + if(INCLUDE_WHAT_YOU_USE) + set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE ${INCLUDE_WHAT_YOU_USE}) + else() + message( + ${WARNING_MESSAGE} + "include-what-you-use requested but executable not found" + ) + endif() +endmacro() diff --git a/cmake/Utilities.cmake b/cmake/Utilities.cmake new file mode 100644 index 00000000..ec164b8b --- /dev/null +++ b/cmake/Utilities.cmake @@ -0,0 +1,86 @@ +# find a substring from a string by a given prefix such as VCVARSALL_ENV_START +function(find_substring_by_prefix output prefix input) + # find the prefix + string(FIND "${input}" "${prefix}" prefix_index) + if("${prefix_index}" STREQUAL "-1") + message(SEND_ERROR "Could not find ${prefix} in ${input}") + endif() + # find the start index + string(LENGTH "${prefix}" prefix_length) + math(EXPR start_index "${prefix_index} + ${prefix_length}") + + string(SUBSTRING "${input}" "${start_index}" "-1" _output) + set("${output}" "${_output}" PARENT_SCOPE) +endfunction() + +# A function to set environment variables of CMake from the output of `cmd /c set` +function(set_env_from_string env_string) + # replace ; in paths with __sep__ so we can split on ; + string(REGEX REPLACE ";" "__sep__" env_string_sep_added "${env_string}") + + # the variables are separated by \r?\n + string(REGEX REPLACE "\r?\n" ";" env_list "${env_string_sep_added}") + + foreach(env_var ${env_list}) + # split by = + string(REGEX REPLACE "=" ";" env_parts "${env_var}") + + list(LENGTH env_parts env_parts_length) + if("${env_parts_length}" EQUAL "2") + # get the variable name and value + list(GET env_parts 0 env_name) + list(GET env_parts 1 env_value) + + # recover ; in paths + string(REGEX REPLACE "__sep__" ";" env_value "${env_value}") + + # set env_name to env_value + set(ENV{${env_name}} "${env_value}") + + # update cmake program path + if("${env_name}" EQUAL "PATH") + list(APPEND CMAKE_PROGRAM_PATH ${env_value}) + endif() + endif() + endforeach() +endfunction() + +function(get_all_targets var) + set(targets) + get_all_targets_recursive(targets ${CMAKE_CURRENT_SOURCE_DIR}) + set(${var} ${targets} PARENT_SCOPE) +endfunction() + +function(get_all_installable_targets var) + set(targets) + get_all_targets(targets) + foreach(_target ${targets}) + get_target_property(_target_type ${_target} TYPE) + if(NOT ${_target_type} MATCHES ".*LIBRARY|EXECUTABLE") + list(REMOVE_ITEM targets ${_target}) + endif() + endforeach() + set(${var} ${targets} PARENT_SCOPE) +endfunction() + +macro(get_all_targets_recursive targets dir) + get_property(subdirectories DIRECTORY ${dir} PROPERTY SUBDIRECTORIES) + foreach(subdir ${subdirectories}) + get_all_targets_recursive(${targets} ${subdir}) + endforeach() + + get_property(current_targets DIRECTORY ${dir} PROPERTY BUILDSYSTEM_TARGETS) + list(APPEND ${targets} ${current_targets}) +endmacro() + +function(is_verbose var) + if( + "CMAKE_MESSAGE_LOG_LEVEL" STREQUAL "VERBOSE" + OR "CMAKE_MESSAGE_LOG_LEVEL" STREQUAL "DEBUG" + OR "CMAKE_MESSAGE_LOG_LEVEL" STREQUAL "TRACE" + ) + set(${var} ON PARENT_SCOPE) + else() + set(${var} OFF PARENT_SCOPE) + endif() +endfunction() diff --git a/cmake/VerifyPkgConfigInstall.cmake b/cmake/VerifyPkgConfigInstall.cmake new file mode 100644 index 00000000..4f199de8 --- /dev/null +++ b/cmake/VerifyPkgConfigInstall.cmake @@ -0,0 +1,349 @@ +cmake_minimum_required(VERSION 4.0...4.3) + +foreach( + required_variable + IN ITEMS + PROJECT_BINARY_DIR + KAHIP_SOURCE_DIR + STAGE_PREFIX + CONSUMER_SOURCE_DIR + CONSUMER_BINARY_DIR + PKG_CONFIG_EXECUTABLE + CTEST_COMMAND + CONSUMER_GENERATOR + C_COMPILER + CXX_COMPILER + INSTALL_BINDIR + INSTALL_LIBDIR + INSTALL_INCLUDEDIR + KAHIP_SHARED_LIBRARY + KAHIP_STATIC_LIBRARY + KAHIP_64BIT + TARGET_WINDOWS + TARGET_UNIX + WITH_PARHIP +) + if(NOT DEFINED ${required_variable} OR "${${required_variable}}" STREQUAL "") + message(FATAL_ERROR "${required_variable} is required") + endif() +endforeach() + +include("${KAHIP_SOURCE_DIR}/cmake/KahipInstallConsumerArguments.cmake") + +if(WITH_PARHIP) + foreach( + required_variable + IN ITEMS + PARHIP_EXECUTABLE + PARHIP_SHARED_LIBRARY + PARHIP_STATIC_LIBRARY + ) + if( + NOT DEFINED ${required_variable} + OR "${${required_variable}}" STREQUAL "" + ) + message(FATAL_ERROR "${required_variable} is required with ParHIP") + endif() + endforeach() +endif() +if(TARGET_WINDOWS) + if(NOT DEFINED KAHIP_LINKER_LIBRARY OR KAHIP_LINKER_LIBRARY STREQUAL "") + message(FATAL_ERROR "KAHIP_LINKER_LIBRARY is required on Windows") + endif() + if( + WITH_PARHIP + AND ( + NOT DEFINED PARHIP_LINKER_LIBRARY + OR PARHIP_LINKER_LIBRARY STREQUAL "" + ) + ) + message( + FATAL_ERROR + "PARHIP_LINKER_LIBRARY is required with ParHIP on Windows" + ) + endif() +endif() + +file(REMOVE_RECURSE "${STAGE_PREFIX}" "${CONSUMER_BINARY_DIR}") + +set(install_command "${CMAKE_COMMAND}" --install "${PROJECT_BINARY_DIR}") +if(NOT "${BUILD_CONFIG}" STREQUAL "") + list(APPEND install_command --config "${BUILD_CONFIG}") +endif() +list(APPEND install_command --prefix "${STAGE_PREFIX}") +execute_process( + COMMAND ${install_command} + RESULT_VARIABLE install_result + OUTPUT_VARIABLE install_stdout + ERROR_VARIABLE install_stderr +) +if(NOT install_result EQUAL 0) + message( + FATAL_ERROR + "staged KaHIP install failed\n${install_stdout}\n${install_stderr}" + ) +endif() + +set(pkg_config_dir "${STAGE_PREFIX}/${INSTALL_LIBDIR}/pkgconfig") +set(shared_library_directory "${INSTALL_LIBDIR}") +if(TARGET_WINDOWS) + set(shared_library_directory "${INSTALL_BINDIR}") +endif() +set(required_installed_files + "${STAGE_PREFIX}/${INSTALL_INCLUDEDIR}/kaHIP_interface.h" + "${STAGE_PREFIX}/${shared_library_directory}/${KAHIP_SHARED_LIBRARY}" + "${STAGE_PREFIX}/${INSTALL_LIBDIR}/${KAHIP_STATIC_LIBRARY}" + "${pkg_config_dir}/kahip.pc" +) +if(TARGET_WINDOWS) + list( + APPEND + required_installed_files + "${STAGE_PREFIX}/${INSTALL_LIBDIR}/${KAHIP_LINKER_LIBRARY}" + ) +endif() +if(WITH_PARHIP) + list( + APPEND + required_installed_files + "${STAGE_PREFIX}/${INSTALL_INCLUDEDIR}/parhip_interface.h" + "${STAGE_PREFIX}/${INSTALL_BINDIR}/${PARHIP_EXECUTABLE}" + "${STAGE_PREFIX}/${shared_library_directory}/${PARHIP_SHARED_LIBRARY}" + "${STAGE_PREFIX}/${INSTALL_LIBDIR}/${PARHIP_STATIC_LIBRARY}" + "${pkg_config_dir}/parhip_interface.pc" + ) + if(TARGET_WINDOWS) + list( + APPEND + required_installed_files + "${STAGE_PREFIX}/${INSTALL_LIBDIR}/${PARHIP_LINKER_LIBRARY}" + ) + endif() +endif() +foreach(installed_file IN LISTS required_installed_files) + if(NOT EXISTS "${installed_file}") + message(FATAL_ERROR "staged install omitted ${installed_file}") + endif() +endforeach() + +if(TARGET_UNIX AND WITH_PARHIP) + if(NOT DEFINED NM_EXECUTABLE OR NM_EXECUTABLE STREQUAL "") + message(FATAL_ERROR "NM_EXECUTABLE is required for ParHIP artifact checks") + endif() + + foreach( + parhip_library + IN ITEMS + "${STAGE_PREFIX}/${shared_library_directory}/${PARHIP_SHARED_LIBRARY}" + "${STAGE_PREFIX}/${INSTALL_LIBDIR}/${PARHIP_STATIC_LIBRARY}" + ) + execute_process( + COMMAND "${NM_EXECUTABLE}" -u "${parhip_library}" + RESULT_VARIABLE nm_result + OUTPUT_VARIABLE undefined_symbols + ERROR_VARIABLE nm_stderr + ) + if(NOT nm_result EQUAL 0) + message( + FATAL_ERROR + "could not inspect installed ParHIP artifact ${parhip_library}\n${nm_stderr}" + ) + endif() + + string(REPLACE "\n" ";" undefined_symbol_lines "${undefined_symbols}") + set(forbidden_lifecycle_symbols "") + foreach(undefined_symbol_line IN LISTS undefined_symbol_lines) + if( + undefined_symbol_line + MATCHES + "(^|[ \t])_?MPI_(Init|Finalize)(@[^ \t]+)?[ \t]*$" + ) + list(APPEND forbidden_lifecycle_symbols "${undefined_symbol_line}") + endif() + endforeach() + if(forbidden_lifecycle_symbols) + list(JOIN forbidden_lifecycle_symbols "\n" symbol_diagnostics) + message( + FATAL_ERROR + "installed ParHIP artifact owns the application MPI lifecycle: ${parhip_library}\n${symbol_diagnostics}" + ) + endif() + endforeach() +endif() + +set(expected_installed_headers kaHIP_interface.h) +if(WITH_PARHIP) + list(APPEND expected_installed_headers parhip_interface.h) +endif() +file( + GLOB_RECURSE installed_headers + LIST_DIRECTORIES FALSE + RELATIVE "${STAGE_PREFIX}/${INSTALL_INCLUDEDIR}" + "${STAGE_PREFIX}/${INSTALL_INCLUDEDIR}/*" +) +list(SORT expected_installed_headers) +list(SORT installed_headers) +if(NOT installed_headers STREQUAL expected_installed_headers) + message( + FATAL_ERROR + "staged install exposed unexpected headers: '${installed_headers}'; expected exactly '${expected_installed_headers}'" + ) +endif() + +set(pkg_config_environment + "PKG_CONFIG_PATH=${pkg_config_dir}" + "PKG_CONFIG_LIBDIR=${pkg_config_dir}" +) +set(pkg_config_modules kahip) +if(WITH_PARHIP) + list(APPEND pkg_config_modules parhip_interface) +endif() +foreach(module IN LISTS pkg_config_modules) + execute_process( + COMMAND + "${CMAKE_COMMAND}" -E env + ${pkg_config_environment} + "${PKG_CONFIG_EXECUTABLE}" --variable=prefix "${module}" + RESULT_VARIABLE prefix_result + OUTPUT_VARIABLE reported_prefix + ERROR_VARIABLE prefix_stderr + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(NOT prefix_result EQUAL 0) + message( + FATAL_ERROR + "pkg-config could not inspect ${module}\n${prefix_stderr}" + ) + endif() + file(REAL_PATH "${reported_prefix}" normalized_reported_prefix) + file(REAL_PATH "${STAGE_PREFIX}" normalized_stage_prefix) + if(NOT normalized_reported_prefix STREQUAL normalized_stage_prefix) + message( + FATAL_ERROR + "${module}.pc reports prefix '${reported_prefix}', expected staged prefix '${STAGE_PREFIX}'" + ) + endif() +endforeach() + +set( + configure_command + "${CMAKE_COMMAND}" + -S "${CONSUMER_SOURCE_DIR}" + -B "${CONSUMER_BINARY_DIR}" + -G "${CONSUMER_GENERATOR}" +) +if(NOT "${CONSUMER_GENERATOR_PLATFORM}" STREQUAL "") + list(APPEND configure_command -A "${CONSUMER_GENERATOR_PLATFORM}") +endif() +if(NOT "${CONSUMER_GENERATOR_TOOLSET}" STREQUAL "") + list(APPEND configure_command -T "${CONSUMER_GENERATOR_TOOLSET}") +endif() +if(NOT "${CONSUMER_GENERATOR_INSTANCE}" STREQUAL "") + list( + APPEND + configure_command + "-DCMAKE_GENERATOR_INSTANCE=${CONSUMER_GENERATOR_INSTANCE}" + ) +endif() +if(NOT "${TOOLCHAIN_FILE}" STREQUAL "") + list(APPEND configure_command "-DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_FILE}") +endif() +list( + APPEND + configure_command + "-DCMAKE_C_COMPILER=${C_COMPILER}" + "-DCMAKE_CXX_COMPILER=${CXX_COMPILER}" + "-DCMAKE_BUILD_TYPE=${BUILD_CONFIG}" + "-DPKG_CONFIG_EXECUTABLE=${PKG_CONFIG_EXECUTABLE}" + "-DWITH_PARHIP=${WITH_PARHIP}" + "-DSTAGE_BINDIR=${STAGE_PREFIX}/${INSTALL_BINDIR}" + "-DSTAGE_INCLUDEDIR=${STAGE_PREFIX}/${INSTALL_INCLUDEDIR}" + "-DSTAGE_LIBDIR=${STAGE_PREFIX}/${INSTALL_LIBDIR}" + "-DKAHIP_STATIC_LIBRARY=${KAHIP_STATIC_LIBRARY}" + "-DPARHIP_STATIC_LIBRARY=${PARHIP_STATIC_LIBRARY}" +) +kahip_append_consumer_cache_argument( + configure_command + KAHIP_64BIT + "${KAHIP_64BIT}" +) +foreach( + context_variable + IN ITEMS + CMAKE_C_FLAGS + CMAKE_CXX_FLAGS + CMAKE_EXE_LINKER_FLAGS + CMAKE_POSITION_INDEPENDENT_CODE + CMAKE_OSX_ARCHITECTURES + CMAKE_OSX_SYSROOT + CMAKE_OSX_DEPLOYMENT_TARGET + VCPKG_INSTALLED_DIR + VCPKG_TARGET_TRIPLET +) + if( + DEFINED ${context_variable} + AND NOT "${${context_variable}}" STREQUAL "" + ) + kahip_append_consumer_cache_argument( + configure_command + "${context_variable}" + "${${context_variable}}" + ) + endif() +endforeach() + +execute_process( + COMMAND + "${CMAKE_COMMAND}" -E env + ${pkg_config_environment} + ${configure_command} + RESULT_VARIABLE configure_result + OUTPUT_VARIABLE configure_stdout + ERROR_VARIABLE configure_stderr +) +if(NOT configure_result EQUAL 0) + message( + FATAL_ERROR + "pkg-config consumer configure failed\n${configure_stdout}\n${configure_stderr}" + ) +endif() + +set(build_command "${CMAKE_COMMAND}" --build "${CONSUMER_BINARY_DIR}") +if(NOT "${BUILD_CONFIG}" STREQUAL "") + list(APPEND build_command --config "${BUILD_CONFIG}") +endif() +list(APPEND build_command --parallel 2) +execute_process( + COMMAND ${build_command} + RESULT_VARIABLE build_result + OUTPUT_VARIABLE build_stdout + ERROR_VARIABLE build_stderr +) +if(NOT build_result EQUAL 0) + message( + FATAL_ERROR + "pkg-config consumer build failed\n${build_stdout}\n${build_stderr}" + ) +endif() + +set( + consumer_test_command + "${CTEST_COMMAND}" + --test-dir "${CONSUMER_BINARY_DIR}" + --output-on-failure +) +if(NOT "${BUILD_CONFIG}" STREQUAL "") + list(APPEND consumer_test_command --build-config "${BUILD_CONFIG}") +endif() +execute_process( + COMMAND ${consumer_test_command} + RESULT_VARIABLE consumer_test_result + OUTPUT_VARIABLE consumer_test_stdout + ERROR_VARIABLE consumer_test_stderr +) +if(NOT consumer_test_result EQUAL 0) + message( + FATAL_ERROR + "pkg-config consumers failed at runtime\n${consumer_test_stdout}\n${consumer_test_stderr}" + ) +endif() diff --git a/cmake/pkgconfig-consumer/CMakeLists.txt b/cmake/pkgconfig-consumer/CMakeLists.txt new file mode 100644 index 00000000..0908b59d --- /dev/null +++ b/cmake/pkgconfig-consumer/CMakeLists.txt @@ -0,0 +1,251 @@ +cmake_minimum_required(VERSION 4.0...4.3) +project(KaHIPPkgConfigConsumer LANGUAGES C CXX) + +set(CMAKE_CXX_SCAN_FOR_MODULES OFF) + +include(CTest) + +if(NOT DEFINED KAHIP_64BIT) + message(FATAL_ERROR "KAHIP_64BIT must match the staged KaHIP package") +endif() + +find_package(PkgConfig REQUIRED) +find_package(OpenMP QUIET) + +function(add_c_api_consumer target) + add_executable(${target} ${ARGN}) + target_compile_features(${target} PRIVATE c_std_17) + # Compile the public headers as C, but use the C++ linker for KaHIP's C++ + # implementation and its transitive runtime dependencies. + set_target_properties(${target} PROPERTIES LINKER_LANGUAGE CXX) +endfunction() + +pkg_check_modules(KAHIP REQUIRED IMPORTED_TARGET kahip) +add_executable(kahip_pkgconfig_consumer kahip_consumer.cpp) +target_compile_features(kahip_pkgconfig_consumer PRIVATE cxx_std_23) +target_link_libraries(kahip_pkgconfig_consumer PRIVATE PkgConfig::KAHIP) +add_test(NAME run-kahip-pkgconfig-consumer COMMAND kahip_pkgconfig_consumer) + +add_c_api_consumer( + kahip_c_pkgconfig_consumer + kahip_c_consumer.c + kahip_c_constants.c +) +target_link_libraries( + kahip_c_pkgconfig_consumer + PRIVATE PkgConfig::KAHIP +) +add_test( + NAME run-kahip-c-pkgconfig-consumer + COMMAND kahip_c_pkgconfig_consumer +) + +add_library(kahip_installed_static STATIC IMPORTED) +set_target_properties( + kahip_installed_static + PROPERTIES + IMPORTED_LOCATION "${STAGE_LIBDIR}/${KAHIP_STATIC_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${STAGE_INCLUDEDIR}" +) +if(KAHIP_64BIT) + target_compile_definitions(kahip_installed_static INTERFACE KAHIP_64BIT) +endif() +if(OpenMP_CXX_FOUND) + target_link_libraries( + kahip_installed_static + INTERFACE OpenMP::OpenMP_CXX + ) +endif() +add_executable(kahip_static_consumer kahip_consumer.cpp) +target_compile_features(kahip_static_consumer PRIVATE cxx_std_23) +target_link_libraries(kahip_static_consumer PRIVATE kahip_installed_static) +add_test(NAME run-kahip-static-consumer COMMAND kahip_static_consumer) + +add_c_api_consumer( + kahip_c_static_consumer + kahip_c_consumer.c + kahip_c_constants.c +) +target_link_libraries( + kahip_c_static_consumer + PRIVATE kahip_installed_static +) +add_test(NAME run-kahip-c-static-consumer COMMAND kahip_c_static_consumer) + +if(WITH_PARHIP) + find_package(MPI 3.1 REQUIRED COMPONENTS C CXX) + pkg_check_modules(PARHIP REQUIRED IMPORTED_TARGET parhip_interface) + add_executable(parhip_pkgconfig_consumer parhip_consumer.cpp) + target_compile_features(parhip_pkgconfig_consumer PRIVATE cxx_std_23) + target_link_libraries(parhip_pkgconfig_consumer PRIVATE PkgConfig::PARHIP) + add_test( + NAME run-parhip-pkgconfig-consumer + COMMAND parhip_pkgconfig_consumer + ) + + add_c_api_consumer( + parhip_c_pkgconfig_consumer + parhip_c_consumer.c + parhip_c_constants.c + ) + target_link_libraries( + parhip_c_pkgconfig_consumer + PRIVATE PkgConfig::PARHIP + ) + add_test( + NAME run-parhip-c-pkgconfig-consumer + COMMAND parhip_c_pkgconfig_consumer + ) + + add_executable(combined_pkgconfig_consumer combined_static_consumer.cpp) + target_compile_features(combined_pkgconfig_consumer PRIVATE cxx_std_23) + target_link_libraries( + combined_pkgconfig_consumer + PRIVATE PkgConfig::KAHIP PkgConfig::PARHIP + ) + add_test( + NAME run-combined-pkgconfig-consumer + COMMAND combined_pkgconfig_consumer + ) + add_executable( + combined_reverse_pkgconfig_consumer + combined_reverse_consumer.cpp + ) + target_compile_features( + combined_reverse_pkgconfig_consumer + PRIVATE cxx_std_23 + ) + target_link_libraries( + combined_reverse_pkgconfig_consumer + PRIVATE PkgConfig::KAHIP PkgConfig::PARHIP + ) + add_test( + NAME run-combined-reverse-pkgconfig-consumer + COMMAND combined_reverse_pkgconfig_consumer + ) + add_c_api_consumer( + combined_c_pkgconfig_consumer + combined_c_consumer.c + combined_reverse_c_constants.c + ) + target_link_libraries( + combined_c_pkgconfig_consumer + PRIVATE PkgConfig::KAHIP PkgConfig::PARHIP + ) + add_test( + NAME run-combined-c-pkgconfig-consumer + COMMAND combined_c_pkgconfig_consumer + ) + + add_library(parhip_installed_static STATIC IMPORTED) + set_target_properties( + parhip_installed_static + PROPERTIES + IMPORTED_LOCATION "${STAGE_LIBDIR}/${PARHIP_STATIC_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${STAGE_INCLUDEDIR}" + ) + target_link_libraries( + parhip_installed_static + INTERFACE MPI::MPI_CXX + ) + if(OpenMP_CXX_FOUND) + target_link_libraries( + parhip_installed_static + INTERFACE OpenMP::OpenMP_CXX + ) + endif() + add_executable(parhip_static_consumer parhip_consumer.cpp) + target_compile_features(parhip_static_consumer PRIVATE cxx_std_23) + target_link_libraries( + parhip_static_consumer + PRIVATE parhip_installed_static + ) + add_test(NAME run-parhip-static-consumer COMMAND parhip_static_consumer) + + add_c_api_consumer( + parhip_c_static_consumer + parhip_c_consumer.c + parhip_c_constants.c + ) + target_link_libraries( + parhip_c_static_consumer + PRIVATE parhip_installed_static + ) + add_test( + NAME run-parhip-c-static-consumer + COMMAND parhip_c_static_consumer + ) + + add_executable(combined_static_consumer combined_static_consumer.cpp) + target_compile_features(combined_static_consumer PRIVATE cxx_std_23) + target_link_libraries( + combined_static_consumer + PRIVATE kahip_installed_static parhip_installed_static + ) + add_test(NAME run-combined-static-consumer COMMAND combined_static_consumer) + add_executable( + combined_reverse_static_consumer + combined_reverse_consumer.cpp + ) + target_compile_features( + combined_reverse_static_consumer + PRIVATE cxx_std_23 + ) + target_link_libraries( + combined_reverse_static_consumer + PRIVATE kahip_installed_static parhip_installed_static + ) + add_test( + NAME run-combined-reverse-static-consumer + COMMAND combined_reverse_static_consumer + ) + add_c_api_consumer( + combined_c_static_consumer + combined_c_consumer.c + combined_reverse_c_constants.c + ) + target_link_libraries( + combined_c_static_consumer + PRIVATE kahip_installed_static parhip_installed_static + ) + add_test( + NAME run-combined-c-static-consumer + COMMAND combined_c_static_consumer + ) +endif() + +if(WIN32) + set(loader_environment "PATH=path_list_prepend:${STAGE_BINDIR}") +elseif(APPLE) + set( + loader_environment + "DYLD_LIBRARY_PATH=path_list_prepend:${STAGE_LIBDIR}" + ) +else() + set( + loader_environment + "LD_LIBRARY_PATH=path_list_prepend:${STAGE_LIBDIR}" + ) +endif() +set_tests_properties( + run-kahip-pkgconfig-consumer + run-kahip-c-pkgconfig-consumer + run-kahip-static-consumer + run-kahip-c-static-consumer + PROPERTIES ENVIRONMENT_MODIFICATION "${loader_environment}" +) +if(WITH_PARHIP) + set_tests_properties( + run-parhip-pkgconfig-consumer + run-parhip-c-pkgconfig-consumer + run-combined-pkgconfig-consumer + run-combined-reverse-pkgconfig-consumer + run-combined-c-pkgconfig-consumer + run-parhip-static-consumer + run-parhip-c-static-consumer + run-combined-static-consumer + run-combined-reverse-static-consumer + run-combined-c-static-consumer + PROPERTIES ENVIRONMENT_MODIFICATION "${loader_environment}" + ) +endif() diff --git a/cmake/pkgconfig-consumer/combined_c_consumer.c b/cmake/pkgconfig-consumer/combined_c_consumer.c new file mode 100644 index 00000000..51a64dcc --- /dev/null +++ b/cmake/pkgconfig-consumer/combined_c_consumer.c @@ -0,0 +1,19 @@ +#include +#include + +int combined_reverse_c_constants_are_valid(void); + +int main(void) { + int (*serial_size_function)(void) = kahip_sizeof_idx; + void (*parallel_partition_function)( + idxtype*, idxtype*, idxtype*, idxtype*, idxtype*, int*, double*, bool, + int, int, int*, idxtype*, MPI_Comm*) = ParHIPPartitionKWay; + + return KAHIP_FASTSOCIAL == 3 && KAHIP_ECOSOCIAL == 4 && + PARHIP_FASTSOCIAL == 4 && PARHIP_ECOSOCIAL == 5 && + combined_reverse_c_constants_are_valid() && + serial_size_function != 0 && + parallel_partition_function != 0 + ? 0 + : 1; +} diff --git a/cmake/pkgconfig-consumer/combined_reverse_c_constants.c b/cmake/pkgconfig-consumer/combined_reverse_c_constants.c new file mode 100644 index 00000000..460bff0b --- /dev/null +++ b/cmake/pkgconfig-consumer/combined_reverse_c_constants.c @@ -0,0 +1,7 @@ +#include +#include + +int combined_reverse_c_constants_are_valid(void) { + return KAHIP_FASTSOCIAL == 3 && KAHIP_ECOSOCIAL == 4 && + PARHIP_FASTSOCIAL == 4 && PARHIP_ECOSOCIAL == 5; +} diff --git a/cmake/pkgconfig-consumer/combined_reverse_consumer.cpp b/cmake/pkgconfig-consumer/combined_reverse_consumer.cpp new file mode 100644 index 00000000..cd56d3db --- /dev/null +++ b/cmake/pkgconfig-consumer/combined_reverse_consumer.cpp @@ -0,0 +1,19 @@ +#include +#include + +namespace { +using parhip_function = decltype(&ParHIPPartitionKWay); +parhip_function volatile parhip_partition = &ParHIPPartitionKWay; +} // namespace + +auto main() -> int { + auto const serial_api_is_valid = + kahip_sizeof_idx() == static_cast(sizeof(kahip_idx)); + auto const social_modes_are_unambiguous = + KAHIP_FASTSOCIAL == 3 && KAHIP_ECOSOCIAL == 4 && + PARHIP_FASTSOCIAL == 4 && PARHIP_ECOSOCIAL == 5; + return serial_api_is_valid && social_modes_are_unambiguous && + parhip_partition != nullptr + ? 0 + : 1; +} diff --git a/cmake/pkgconfig-consumer/combined_static_consumer.cpp b/cmake/pkgconfig-consumer/combined_static_consumer.cpp new file mode 100644 index 00000000..674f37b8 --- /dev/null +++ b/cmake/pkgconfig-consumer/combined_static_consumer.cpp @@ -0,0 +1,19 @@ +#include +#include + +namespace { +using parhip_function = decltype(&ParHIPPartitionKWay); +parhip_function volatile parhip_partition = &ParHIPPartitionKWay; +} // namespace + +auto main() -> int { + auto const serial_api_is_valid = + kahip_sizeof_idx() == static_cast(sizeof(kahip_idx)); + auto const social_modes_are_unambiguous = + KAHIP_FASTSOCIAL == 3 && KAHIP_ECOSOCIAL == 4 && + PARHIP_FASTSOCIAL == 4 && PARHIP_ECOSOCIAL == 5; + return serial_api_is_valid && social_modes_are_unambiguous && + parhip_partition != nullptr + ? 0 + : 1; +} diff --git a/cmake/pkgconfig-consumer/kahip_c_constants.c b/cmake/pkgconfig-consumer/kahip_c_constants.c new file mode 100644 index 00000000..e1940f2a --- /dev/null +++ b/cmake/pkgconfig-consumer/kahip_c_constants.c @@ -0,0 +1,8 @@ +#include + +int kahip_c_constants_are_valid(void) { + return FAST == 0 && ECO == 1 && STRONG == 2 && + KAHIP_FASTSOCIAL == 3 && KAHIP_ECOSOCIAL == 4 && + STRONGSOCIAL == 5 && MAPMODE_MULTISECTION == 0 && + MAPMODE_BISECTION == 1; +} diff --git a/cmake/pkgconfig-consumer/kahip_c_consumer.c b/cmake/pkgconfig-consumer/kahip_c_consumer.c new file mode 100644 index 00000000..0255111b --- /dev/null +++ b/cmake/pkgconfig-consumer/kahip_c_consumer.c @@ -0,0 +1,15 @@ +#include + +int kahip_c_constants_are_valid(void); + +int main(void) { + void (*edge_partitioning_function)( + int*, int*, kahip_idx*, kahip_idx*, kahip_idx*, int*, double*, bool, + int, int, int*, int*, kahip_idx) = edge_partitioning; + + return kahip_sizeof_idx() == (int)sizeof(kahip_idx) && + kahip_c_constants_are_valid() && + edge_partitioning_function != 0 + ? 0 + : 1; +} diff --git a/cmake/pkgconfig-consumer/kahip_consumer.cpp b/cmake/pkgconfig-consumer/kahip_consumer.cpp new file mode 100644 index 00000000..6451cbbc --- /dev/null +++ b/cmake/pkgconfig-consumer/kahip_consumer.cpp @@ -0,0 +1,43 @@ +#include + +#include +#include +#include +#include + +auto main() -> int { + if (kahip_sizeof_idx() != static_cast(sizeof(kahip_idx))) { + return 1; + } + + auto vertex_count = 4; + auto offsets = std::array{0, 2, 4, 6, 8}; + auto neighbors = std::array{1, 3, 0, 2, 1, 3, 0, 2}; + auto blocks = 2; + auto imbalance = 0.03; + auto edge_cut = kahip_idx{-1}; + auto partition = std::array{}; + + kaffpa(&vertex_count, nullptr, offsets.data(), nullptr, neighbors.data(), + &blocks, &imbalance, true, 1, FAST, &edge_cut, partition.data()); + + auto block_weights = std::array{}; + for (auto const block : partition) { + if (block < 0 || block >= blocks) { + return 1; + } + ++block_weights[static_cast(block)]; + } + auto const recomputed_cut = + static_cast(std::ranges::count_if( + std::views::iota(std::size_t{0}, partition.size()), + [&](std::size_t vertex) { + return partition[vertex] != + partition[(vertex + 1) % partition.size()]; + })); + return std::ranges::all_of(block_weights, + [](int weight) { return weight <= 2; }) && + edge_cut == recomputed_cut + ? 0 + : 1; +} diff --git a/cmake/pkgconfig-consumer/parhip_c_constants.c b/cmake/pkgconfig-consumer/parhip_c_constants.c new file mode 100644 index 00000000..ec7704bd --- /dev/null +++ b/cmake/pkgconfig-consumer/parhip_c_constants.c @@ -0,0 +1,7 @@ +#include + +int parhip_c_constants_are_valid(void) { + return ULTRAFASTMESH == 0 && FASTMESH == 1 && ECOMESH == 2 && + ULTRAFASTSOCIAL == 3 && PARHIP_FASTSOCIAL == 4 && + PARHIP_ECOSOCIAL == 5; +} diff --git a/cmake/pkgconfig-consumer/parhip_c_consumer.c b/cmake/pkgconfig-consumer/parhip_c_consumer.c new file mode 100644 index 00000000..f4c14e51 --- /dev/null +++ b/cmake/pkgconfig-consumer/parhip_c_consumer.c @@ -0,0 +1,11 @@ +#include + +int parhip_c_constants_are_valid(void); + +int main(void) { + void (*partition_function)(idxtype*, idxtype*, idxtype*, idxtype*, + idxtype*, int*, double*, bool, int, int, int*, + idxtype*, MPI_Comm*) = ParHIPPartitionKWay; + + return parhip_c_constants_are_valid() && partition_function != 0 ? 0 : 1; +} diff --git a/cmake/pkgconfig-consumer/parhip_consumer.cpp b/cmake/pkgconfig-consumer/parhip_consumer.cpp new file mode 100644 index 00000000..4711150c --- /dev/null +++ b/cmake/pkgconfig-consumer/parhip_consumer.cpp @@ -0,0 +1,30 @@ +#include + +#include +#include + +auto main(int argc, char** argv) -> int { + if (MPI_Init(&argc, &argv) != MPI_SUCCESS) { + return 1; + } + + std::array vertex_distribution{0, 4}; + std::array edge_offsets{0, 2, 4, 6, 8}; + std::array neighbors{1, 3, 0, 2, 1, 3, 0, 2}; + std::array partition{}; + auto block_count = 2; + auto imbalance = 0.03; + auto edge_cut = -1; + auto communicator = MPI_COMM_WORLD; + + ParHIPPartitionKWay(vertex_distribution.data(), edge_offsets.data(), + neighbors.data(), nullptr, nullptr, &block_count, + &imbalance, true, 1, FASTMESH, &edge_cut, + partition.data(), &communicator); + + auto const valid_partition = [](idxtype block) { return block < 2; }; + auto const partition_is_valid = + edge_cut >= 0 && std::ranges::all_of(partition, valid_partition); + auto const finalize_succeeded = MPI_Finalize() == MPI_SUCCESS; + return partition_is_valid && finalize_succeeded ? 0 : 1; +} diff --git a/cmake/tests/CaptureInstallConsumerArguments.cmake b/cmake/tests/CaptureInstallConsumerArguments.cmake new file mode 100644 index 00000000..40966c00 --- /dev/null +++ b/cmake/tests/CaptureInstallConsumerArguments.cmake @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 4.0...4.3) + +if(NOT DEFINED OUTPUT_FILE OR OUTPUT_FILE STREQUAL "") + message(FATAL_ERROR "OUTPUT_FILE is required") +endif() + +file( + WRITE "${OUTPUT_FILE}" + "architectures=${CMAKE_OSX_ARCHITECTURES}\n" + "sysroot=${CMAKE_OSX_SYSROOT}\n" + "deployment_target=${CMAKE_OSX_DEPLOYMENT_TARGET}\n" + "vcpkg_installed_dir=${VCPKG_INSTALLED_DIR}\n" + "vcpkg_target_triplet=${VCPKG_TARGET_TRIPLET}\n" + "kahip_64bit=${KAHIP_64BIT}\n" + "target_windows=${TARGET_WINDOWS}\n" +) diff --git a/cmake/tests/VerifyInstallConsumerArguments.cmake b/cmake/tests/VerifyInstallConsumerArguments.cmake new file mode 100644 index 00000000..640d71c1 --- /dev/null +++ b/cmake/tests/VerifyInstallConsumerArguments.cmake @@ -0,0 +1,82 @@ +cmake_minimum_required(VERSION 4.0...4.3) + +foreach(required IN ITEMS KAHIP_SOURCE_DIR WORK_DIRECTORY) + if(NOT DEFINED ${required} OR "${${required}}" STREQUAL "") + message(FATAL_ERROR "${required} is required") + endif() +endforeach() + +include("${KAHIP_SOURCE_DIR}/cmake/KahipInstallConsumerArguments.cmake") + +file(REMOVE_RECURSE "${WORK_DIRECTORY}") +file(MAKE_DIRECTORY "${WORK_DIRECTORY}") +set(captured_arguments "${WORK_DIRECTORY}/captured-arguments.txt") +set( + configure_command + "${CMAKE_COMMAND}" + "-DOUTPUT_FILE=${captured_arguments}" +) +kahip_append_consumer_cache_argument( + configure_command + CMAKE_OSX_ARCHITECTURES + "arm64;x86_64" +) +kahip_append_consumer_cache_argument( + configure_command + CMAKE_OSX_SYSROOT + "/SDKs/MacOSX.sdk" +) +kahip_append_consumer_cache_argument( + configure_command + CMAKE_OSX_DEPLOYMENT_TARGET + "14.0" +) +kahip_append_consumer_cache_argument( + configure_command + VCPKG_INSTALLED_DIR + "/tmp/vcpkg installed" +) +kahip_append_consumer_cache_argument( + configure_command + VCPKG_TARGET_TRIPLET + "arm64-osx" +) +kahip_append_consumer_cache_argument(configure_command KAHIP_64BIT "ON") +kahip_append_consumer_cache_argument(configure_command TARGET_WINDOWS "OFF") +list( + APPEND + configure_command + -P + "${KAHIP_SOURCE_DIR}/cmake/tests/CaptureInstallConsumerArguments.cmake" +) + +execute_process( + COMMAND ${configure_command} + RESULT_VARIABLE capture_result + OUTPUT_VARIABLE capture_stdout + ERROR_VARIABLE capture_stderr +) +if(NOT capture_result EQUAL 0) + message( + FATAL_ERROR + "consumer argument capture failed\n${capture_stdout}\n${capture_stderr}" + ) +endif() + +file(READ "${captured_arguments}" actual_arguments) +string( + CONCAT expected_arguments + "architectures=arm64;x86_64\n" + "sysroot=/SDKs/MacOSX.sdk\n" + "deployment_target=14.0\n" + "vcpkg_installed_dir=/tmp/vcpkg installed\n" + "vcpkg_target_triplet=arm64-osx\n" + "kahip_64bit=ON\n" + "target_windows=OFF\n" +) +if(NOT actual_arguments STREQUAL expected_arguments) + message( + FATAL_ERROR + "consumer cache arguments changed in transit\nexpected:\n${expected_arguments}actual:\n${actual_arguments}" + ) +endif() diff --git a/cmake/tests/VerifyKahipPkgConfigAbi.cmake b/cmake/tests/VerifyKahipPkgConfigAbi.cmake new file mode 100644 index 00000000..1b432af5 --- /dev/null +++ b/cmake/tests/VerifyKahipPkgConfigAbi.cmake @@ -0,0 +1,90 @@ +cmake_minimum_required(VERSION 4.0...4.3) + +foreach( + required + IN ITEMS KAHIP_SOURCE_DIR PKG_CONFIG_EXECUTABLE WORK_DIRECTORY +) + if(NOT DEFINED ${required} OR "${${required}}" STREQUAL "") + message(FATAL_ERROR "${required} is required") + endif() +endforeach() + +file(REMOVE_RECURSE "${WORK_DIRECTORY}") +file(MAKE_DIRECTORY "${WORK_DIRECTORY}") +list(APPEND CMAKE_MODULE_PATH "${KAHIP_SOURCE_DIR}/cmake") +include(KahipPkgConfig) + +function(verify_kahip_abi_case case_name expect_64_bit expect_metis) + set(case_directory "${WORK_DIRECTORY}/${case_name}") + file(MAKE_DIRECTORY "${case_directory}") + set(KAHIP_PKGCONFIG_PREFIX_FROM_PCFILEDIR "../..") + set(CMAKE_INSTALL_INCLUDEDIR include) + set(CMAKE_INSTALL_LIBDIR lib) + set(PROJECT_VERSION 3.24) + set(api_options "") + if(expect_64_bit) + list(APPEND api_options 64BIT) + endif() + if(expect_metis) + list(APPEND api_options METIS) + endif() + kahip_format_serial_api_pkg_config_cflags( + KAHIP_PKGCONFIG_CFLAGS + ${api_options} + ) + configure_file( + "${KAHIP_SOURCE_DIR}/lib/kahip.pc.in" + "${case_directory}/kahip.pc" + @ONLY + ) + + execute_process( + COMMAND + "${CMAKE_COMMAND}" -E env + "PKG_CONFIG_PATH=${case_directory}" + "PKG_CONFIG_LIBDIR=${case_directory}" + "${PKG_CONFIG_EXECUTABLE}" --cflags kahip + RESULT_VARIABLE pkg_config_result + OUTPUT_VARIABLE pkg_config_output + ERROR_VARIABLE pkg_config_stderr + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(NOT pkg_config_result EQUAL 0) + message( + FATAL_ERROR + "pkg-config failed for ${case_name}\n${pkg_config_stderr}" + ) + endif() + + separate_arguments(parsed_cflags UNIX_COMMAND "${pkg_config_output}") + list(FIND parsed_cflags -DKAHIP_64BIT definition_index) + if(expect_64_bit AND definition_index EQUAL -1) + message( + FATAL_ERROR + "64-bit kahip.pc omitted -DKAHIP_64BIT: ${pkg_config_output}" + ) + elseif(NOT expect_64_bit AND NOT definition_index EQUAL -1) + message( + FATAL_ERROR + "32-bit kahip.pc unexpectedly advertised -DKAHIP_64BIT: ${pkg_config_output}" + ) + endif() + + list(FIND parsed_cflags -DUSEMETIS metis_definition_index) + if(expect_metis AND metis_definition_index EQUAL -1) + message( + FATAL_ERROR + "Metis-enabled kahip.pc omitted -DUSEMETIS: ${pkg_config_output}" + ) + elseif(NOT expect_metis AND NOT metis_definition_index EQUAL -1) + message( + FATAL_ERROR + "non-Metis kahip.pc unexpectedly advertised -DUSEMETIS: ${pkg_config_output}" + ) + endif() +endfunction() + +verify_kahip_abi_case(32-bit FALSE FALSE) +verify_kahip_abi_case(64-bit TRUE FALSE) +verify_kahip_abi_case(metis-32-bit FALSE TRUE) +verify_kahip_abi_case(metis-64-bit TRUE TRUE) diff --git a/cmake/tests/VerifyMpiPkgConfigFlags.cmake b/cmake/tests/VerifyMpiPkgConfigFlags.cmake new file mode 100644 index 00000000..b3af0b60 --- /dev/null +++ b/cmake/tests/VerifyMpiPkgConfigFlags.cmake @@ -0,0 +1,105 @@ +cmake_minimum_required(VERSION 4.0...4.3) + +foreach(required IN ITEMS KAHIP_SOURCE_DIR PKG_CONFIG_EXECUTABLE WORK_DIRECTORY) + if(NOT DEFINED ${required} OR "${${required}}" STREQUAL "") + message(FATAL_ERROR "${required} is required") + endif() +endforeach() + +include("${KAHIP_SOURCE_DIR}/cmake/KahipPkgConfig.cmake") + +kahip_format_mpi_pkg_config_flags( + mpi_cflags + mpi_libs + INCLUDE_DIRECTORIES "/opt/MPI SDK/include" + COMPILE_DEFINITIONS "MPI_FEATURE=1" + COMPILE_OPTIONS -pthread -fopenmp + LINK_OPTIONS -pthread -Wl,--as-needed + LIBRARIES "/opt/MPI SDK/lib/libmpi.so" mpi_cxx +) + +set( + expected_cflags + "-I/opt/MPI\\ SDK/include -DMPI_FEATURE=1 -pthread -fopenmp" +) +set( + expected_libs + "-pthread -Wl,--as-needed /opt/MPI\\ SDK/lib/libmpi.so -lmpi_cxx" +) + +if(NOT mpi_cflags STREQUAL expected_cflags) + message( + FATAL_ERROR + "unexpected MPI Cflags\nexpected: ${expected_cflags}\nactual: ${mpi_cflags}" + ) +endif() +if(NOT mpi_libs STREQUAL expected_libs) + message( + FATAL_ERROR + "unexpected MPI Libs\nexpected: ${expected_libs}\nactual: ${mpi_libs}" + ) +endif() + +file(REMOVE_RECURSE "${WORK_DIRECTORY}") +file(MAKE_DIRECTORY "${WORK_DIRECTORY}") +set(synthetic_pc "${WORK_DIRECTORY}/kahip-mpi-flags.pc") +file( + WRITE "${synthetic_pc}" + "Name: kahip-mpi-flags\n" + "Description: synthetic MPI flag round-trip fixture\n" + "Version: 1\n" + "Cflags: ${mpi_cflags}\n" + "Libs: ${mpi_libs}\n" +) + +set( + pkg_config_environment + "PKG_CONFIG_PATH=${WORK_DIRECTORY}" + "PKG_CONFIG_LIBDIR=${WORK_DIRECTORY}" +) +foreach(flag_kind IN ITEMS cflags libs) + execute_process( + COMMAND + "${CMAKE_COMMAND}" -E env + ${pkg_config_environment} + "${PKG_CONFIG_EXECUTABLE}" --${flag_kind} kahip-mpi-flags + RESULT_VARIABLE pkg_config_result + OUTPUT_VARIABLE pkg_config_output + ERROR_VARIABLE pkg_config_error + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(NOT pkg_config_result EQUAL 0) + message( + FATAL_ERROR + "pkg-config --${flag_kind} failed\n${pkg_config_error}" + ) + endif() + separate_arguments(parsed_${flag_kind} UNIX_COMMAND "${pkg_config_output}") +endforeach() + +set( + expected_parsed_cflags + "-I/opt/MPI SDK/include" + -DMPI_FEATURE=1 + -pthread + -fopenmp +) +set( + expected_parsed_libs + -pthread + -Wl,--as-needed + "/opt/MPI SDK/lib/libmpi.so" + -lmpi_cxx +) +if(NOT parsed_cflags STREQUAL expected_parsed_cflags) + message( + FATAL_ERROR + "pkg-config changed MPI Cflags tokenization\nexpected: ${expected_parsed_cflags}\nactual: ${parsed_cflags}" + ) +endif() +if(NOT parsed_libs STREQUAL expected_parsed_libs) + message( + FATAL_ERROR + "pkg-config changed MPI Libs tokenization\nexpected: ${expected_parsed_libs}\nactual: ${parsed_libs}" + ) +endif() diff --git a/cmake/tests/VerifyObjectArchitecture.cmake b/cmake/tests/VerifyObjectArchitecture.cmake new file mode 100644 index 00000000..b20a1056 --- /dev/null +++ b/cmake/tests/VerifyObjectArchitecture.cmake @@ -0,0 +1,197 @@ +cmake_minimum_required(VERSION 4.0) + +if(NOT DEFINED KAHIP_SOURCE_DIR) + message(FATAL_ERROR "KAHIP_SOURCE_DIR is required") +endif() + +function(require_source_pattern file pattern diagnostic) + file(READ "${file}" source) + if(NOT source MATCHES "${pattern}") + message(FATAL_ERROR "${diagnostic}") + endif() +endfunction() + +function(reject_source_pattern file pattern diagnostic) + file(READ "${file}" source) + if(source MATCHES "${pattern}") + message(FATAL_ERROR "${diagnostic}") + endif() +endfunction() + +set(root_cmake "${KAHIP_SOURCE_DIR}/CMakeLists.txt") +set(modified_cmake "${KAHIP_SOURCE_DIR}/parallel/modified_kahip/CMakeLists.txt") +set(parhip_cmake "${KAHIP_SOURCE_DIR}/parallel/parallel_src/CMakeLists.txt") + +require_source_pattern( + "${root_cmake}" + "cmake_minimum_required\\(VERSION 4\\.0\\.\\.\\.4\\.3\\)" + "KaHIP must require the tested CMake 4.0 policy range" +) +foreach( + object_target + IN ITEMS + kahip_core_obj + kahip_collective_obj + kahip_mapping_obj + kahip_spac_obj + kahip_ordering_obj +) + require_source_pattern( + "${root_cmake}" + "add_library\\([^\\)]*${object_target}[^\\)]*OBJECT" + "missing root object target ${object_target}" + ) +endforeach() + +foreach( + object_target + IN ITEMS + modified_kahip_core_obj + modified_kahip_collective_obj + modified_kahip_evolutionary_interface_obj +) + require_source_pattern( + "${modified_cmake}" + "add_library\\([^\\)]*${object_target}[^\\)]*OBJECT" + "missing modified-KaHIP object target ${object_target}" + ) +endforeach() + +foreach( + object_target + IN ITEMS + parhip_graph_obj + parhip_mpi_obj + parhip_mpi_application_obj + parhip_core_obj + parhip_dspac_obj +) + require_source_pattern( + "${parhip_cmake}" + "add_library\\([^\\)]*${object_target}[^\\)]*OBJECT" + "missing ParHIP object target ${object_target}" + ) +endforeach() + +foreach(cmake_file IN ITEMS "${root_cmake}" "${modified_cmake}" "${parhip_cmake}") + require_source_pattern( + "${cmake_file}" + "FILE_SET" + "${cmake_file} must declare target-local header file sets" + ) +endforeach() + +foreach( + legacy_include_variable + IN ITEMS + KAHIP_PRIVATE_INCLUDE_DIRS + MODIFIED_KAHIP_PRIVATE_INCLUDE_DIRS + PARHIP_PRIVATE_INCLUDE_DIRS +) + foreach( + cmake_file + IN ITEMS "${root_cmake}" "${modified_cmake}" "${parhip_cmake}" + ) + reject_source_pattern( + "${cmake_file}" + "${legacy_include_variable}" + "legacy include-directory aggregate ${legacy_include_variable} must be represented by target-local header file sets" + ) + endforeach() +endforeach() + +require_source_pattern( + "${root_cmake}" + "function\\(kahip_add_header_root_file_sets" + "KaHIP must model legacy header roots with target-local CMake file sets" +) + +foreach( + configured_target + IN ITEMS + kahip_core_obj + kahip_collective_obj + kahip_mapping_obj + kahip_spac_obj + kahip_ordering_obj +) + require_source_pattern( + "${root_cmake}" + "kahip_configure_root_object\\(${configured_target}\\)[ \t\r\n]+kahip_add_private_header_set\\([ \t\r\n]+${configured_target}" + "${configured_target} header roots must precede its catch-all private header file set" + ) +endforeach() + +require_source_pattern( + "${parhip_cmake}" + "FILE_SET[ \t\r\n]+parhip_generated_headers[ \t\r\n]+TYPE[ \t\r\n]+HEADERS[ \t\r\n]+BASE_DIRS[ \t\r\n]+\"\\\$\\{PARHIP_GENERATED_INCLUDE_DIR\\}\"[ \t\r\n]+FILES[ \t\r\n]+\"\\\$\\{PARHIP_GENERATED_INCLUDE_DIR\\}/kahip_mpi_capabilities\\.h\"" + "the generated MPI capability header must be an actual FILE_SET member" +) + +foreach( + configured_target + IN ITEMS + modified_kahip_core_obj + modified_kahip_collective_obj + modified_kahip_evolutionary_interface_obj +) + require_source_pattern( + "${modified_cmake}" + "kahip_configure_modified_object\\(${configured_target}\\)[ \t\r\n]+kahip_add_private_header_set\\([ \t\r\n]+${configured_target}" + "${configured_target} header roots must precede shared or catch-all header file sets" + ) +endforeach() + +foreach( + configured_target + IN ITEMS + parhip_graph_obj + parhip_mpi_obj + parhip_mpi_application_obj + parhip_core_obj + parhip_dspac_obj +) + require_source_pattern( + "${parhip_cmake}" + "kahip_configure_parhip_object\\(${configured_target}\\)[ \t\r\n]+kahip_add_private_header_set\\([ \t\r\n]+${configured_target}" + "${configured_target} header roots must precede its catch-all private header file set" + ) +endforeach() + +foreach( + internal_target + IN ITEMS + interface_test + kahip + libmodified_kahip_interface + parallel + libedgelist + libdspac +) + foreach( + cmake_file + IN ITEMS "${root_cmake}" "${modified_cmake}" "${parhip_cmake}" + ) + reject_source_pattern( + "${cmake_file}" + "target_include_directories\\([ \t\r\n]*${internal_target}([ \t\r\n]|\\))" + "internal target ${internal_target} must express project headers through FILE_SET HEADERS" + ) + endforeach() +endforeach() + +reject_source_pattern( + "${root_cmake}" + "lib/tools/(graph_communication|mpi_tools)\\.cpp" + "dead root point-to-point MPI helpers remain in a target" +) +reject_source_pattern( + "${modified_cmake}" + "lib/tools/graph_communication\\.cpp" + "dead modified-KaHIP graph broadcast remains in a target" +) +reject_source_pattern( + "${parhip_cmake}" + "parhip_(graph_io|edge_list)_obj" + "duplicate graph-I/O object targets must be replaced by parhip_graph_obj" +) diff --git a/cmake/tests/VerifySerialCBoundaryFailure.cmake b/cmake/tests/VerifySerialCBoundaryFailure.cmake new file mode 100644 index 00000000..b15eb068 --- /dev/null +++ b/cmake/tests/VerifySerialCBoundaryFailure.cmake @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 4.0) + +if( + NOT DEFINED PROBE + OR NOT DEFINED MODE + OR NOT DEFINED EXPECTED_DIAGNOSTIC + OR NOT DEFINED EXPECTED_INJECTION + OR NOT DEFINED EXPECTED_MARKER +) + message( + FATAL_ERROR + "PROBE, MODE, EXPECTED_DIAGNOSTIC, EXPECTED_INJECTION, and EXPECTED_MARKER are required" + ) +endif() + +execute_process( + COMMAND "${PROBE}" "${MODE}" + RESULT_VARIABLE probe_result + OUTPUT_VARIABLE probe_stdout + ERROR_VARIABLE probe_stderr + TIMEOUT 10 +) +set(probe_output "${probe_stdout}\n${probe_stderr}") +if(NOT "${probe_result}" STREQUAL "86") + message( + FATAL_ERROR + "failure probe returned ${probe_result}, expected 86\n${probe_output}" + ) +endif() +if(NOT probe_output MATCHES "${EXPECTED_DIAGNOSTIC}") + message(FATAL_ERROR "missing fail-fast diagnostic\n${probe_output}") +endif() +if(NOT probe_output MATCHES "${EXPECTED_INJECTION}") + message(FATAL_ERROR "missing failure-injection marker\n${probe_output}") +endif() +if(NOT probe_output MATCHES "${EXPECTED_MARKER}") + message(FATAL_ERROR "missing termination marker\n${probe_output}") +endif() diff --git a/devenv.lock b/devenv.lock new file mode 100644 index 00000000..a16117c4 --- /dev/null +++ b/devenv.lock @@ -0,0 +1,81 @@ +{ + "nodes": { + "devenv": { + "locked": { + "dir": "src/modules", + "lastModified": 1788303825, + "narHash": "sha256-R1tqmYSV5yVTqmy+uISsSn+eEiqgW0F49orOHCOBSts=", + "owner": "cachix", + "repo": "devenv", + "rev": "df5c75a82c3ac3d70a13c90fb869b7e44470a118", + "type": "github" + }, + "original": { + "dir": "src/modules", + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "nixpkgs": { + "inputs": { + "nixpkgs-src": "nixpkgs-src" + }, + "locked": { + "lastModified": 1787753358, + "narHash": "sha256-Tl77VbWyAKrOfRNQhL6JbQtb/MLzbYa/1RG1gWWfICk=", + "owner": "cachix", + "repo": "devenv-nixpkgs", + "rev": "256551e45f6303e142ab4a98be1bf243feb77dc0", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "rolling", + "repo": "devenv-nixpkgs", + "type": "github" + } + }, + "nixpkgs-multiverse": { + "locked": { + "lastModified": 1788363440, + "narHash": "sha256-N+c6IM455k80/K5V35OpvBgjdcdgb54LOsBnQ0w6s+Q=", + "owner": "fzakaria", + "repo": "nixpkgs-multiverse", + "rev": "bf9076961a97bdec39d88898b50516ab8538633d", + "type": "github" + }, + "original": { + "owner": "fzakaria", + "repo": "nixpkgs-multiverse", + "type": "github" + } + }, + "nixpkgs-src": { + "flake": false, + "locked": { + "lastModified": 1787394516, + "narHash": "sha256-pRGOQSClnXNI2iLUG6DYpsGvYcuw0drOutVZFTJNw90=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "c8f90650c15282fa8656a041bfbbd2403997a9a7", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "nixpkgs": "nixpkgs", + "nixpkgs-multiverse": "nixpkgs-multiverse" + } + } + }, + "root": "root", + "version": 7 +} \ No newline at end of file diff --git a/devenv.nix b/devenv.nix new file mode 100644 index 00000000..1a237d46 --- /dev/null +++ b/devenv.nix @@ -0,0 +1,31 @@ +{ pkgs, multiverse, ... }: + +{ + packages = [ + pkgs.cmake + pkgs.ninja + pkgs.pkg-config + pkgs.catch2_3 + pkgs.llvmPackages.clang + # Keep the MPI runtime stable independently of the rolling toolchain. + multiverse.mpich."4.3.2" + ]; + + languages.cplusplus = { + enable = true; + lsp.enable = false; + }; + + tasks = { + "kahip:configure".exec = + "cmake --fresh --preset unix-clang-release -DNONATIVEOPTIMIZATIONS=ON"; + "kahip:build" = { + exec = "cmake --build --preset build-unix-clang-release"; + after = [ "kahip:configure" ]; + }; + "kahip:test" = { + exec = "ctest --preset test-unix-clang-release"; + after = [ "kahip:build" ]; + }; + }; +} diff --git a/devenv.yaml b/devenv.yaml new file mode 100644 index 00000000..675b716b --- /dev/null +++ b/devenv.yaml @@ -0,0 +1,7 @@ +inputs: + nixpkgs: + url: github:cachix/devenv-nixpkgs/rolling + nixpkgs-multiverse: + url: github:fzakaria/nixpkgs-multiverse + +require_version: ">= 2.2.0" diff --git a/docs/plans/2026-09-02-remove-runtime-dependencies.md b/docs/plans/2026-09-02-remove-runtime-dependencies.md new file mode 100644 index 00000000..80b805bf --- /dev/null +++ b/docs/plans/2026-09-02-remove-runtime-dependencies.md @@ -0,0 +1,94 @@ +# KaHIP dependency-removal implementation plan + +## Goal + +Remove KaHIP's production and installed-consumer dependencies on fmt, spdlog, +Boost.Hana, and Boost.MP11 while preserving the explicit MPI wire schema and +fatal-abort behavior. Replace vcpkg as the documented local development path +with a locked devenv environment. Keep CMake 4 as the project baseline and +verify the MPI build with the existing Cirrus toolchains without installing +anything on the cluster. + +## Binding constraints + +- Work in the existing `mpi-collective` checkout and preserve unrelated dirty + changes; do not create a worktree or commit implicitly. +- Keep installed C interfaces and public headers unchanged. +- Use standard streams and small stream helpers; do not use `std::format`, + `std::print`, or standard range formatting. +- Keep MPI metadata explicit and private. Do not vendor Cista or qlibs/reflect. +- The private fatal sink is process-global and non-atomic. It must flush before + the existing abort path and must not turn formatting failures into a new + failure path. +- Retain the existing fatal-path probes; do not add dedicated logging unit tests. +- Require CMake 4.0 or newer. Keep preset schema version 9. +- Local development uses devenv. Existing CI is out of scope and may retain its + vcpkg bootstrap path for Catch2. +- On Cirrus, operate only below + `/work/e609/e609/eriche609/KaHIP`, use existing modules, and install or + download nothing. + +## Task 1: Reconcile the in-flight implementation with the specification + +Inspect the complete dirty diff and run source/build-manifest scans for the four +removed libraries. Verify that no production source, CMake target, package +lookup, installed link interface, or diagnostic test marker still depends on +them. Treat upstream text under vendored `extern/` directories separately from +KaHIP-owned dependency declarations. + +## Task 2: Complete MPI metadata replacement and focused coverage + +Keep the native MPI type map in a `std::tuple` with fold-based membership and a +local tuple-index trait. Keep `wire_members` as explicit member-pointer tuples +iterated with `std::apply`. Verify native-handle alignment, member ordering, +unsupported member rejection, non-default-constructible records, actual-object +address calculation, and `extent == sizeof(T)`. + +## Task 3: Complete fatal diagnostics and stream formatting replacement + +Use the private header-only fatal sink and standard streams throughout. Preserve +the exact payload, one trailing newline, and fallback behavior; use the existing +MPI abort probes to verify the payload and flush ordering. Retain the PMPI +callback-safety scan for the new helper. Verify range joining, CLI and fixture +output, and `mpi_error::what()`. + +## Task 4: Restore CMake 4 and add the local devenv environment + +Restore all KaHIP-owned minimum-version declarations and documentation to CMake +4.0+, leaving preset schema version 9. Remove the vcpkg toolchain from ordinary +local presets. Add `devenv.yaml`, `devenv.nix`, and a committed lockfile that +provide CMake 4+, Ninja, pkg-config, Catch2 3, and MPI on supported local hosts. +Pin the local MPI version through nixpkgs-multiverse, with its input locked +alongside the base toolchain. +Expose small configure/build/test tasks without changing KaHIP's installed +interfaces. Keep CI-only vcpkg files unchanged. + +## Task 5: Verify local dependency closure + +From the devenv shell, configure and build serial and MPI release trees, run the +focused/unit tests available on the host, and stage an install. Build the +pkg-config/static consumer without manually linking any of the four removed +libraries. Run a final KaHIP-owned source and generated-build scan. + +## Task 6: Verify Cirrus compiler compatibility + +Without modifying the remote checkout until the local implementation is ready, +use CMake 4.1.2 and the Cray compiler wrappers with: + +- `PrgEnv-gnu` / GCC 14.2 / Cray MPICH; and +- `PrgEnv-cray` / CCE (Cray Clang) 19.0 / Cray MPICH. + +Keep configure, build, and test output under `out/build/cirrus-*` in the allowed +KaHIP directory. Do not install packages or write outside that directory. Record +any cluster-runtime limitation separately from source or compile failures. + +## Completion evidence + +- Clean KaHIP-owned scan for fmt, spdlog, Hana, and MP11 dependencies and old + marker names. +- Successful CMake 4 serial and MPI compilation with the available local + toolchains. +- Passing focused metadata, diagnostics, CLI/fixture, and install-consumer tests. +- Devenv evaluation plus locked package/version evidence. +- Successful Cirrus GCC 14 and Cray Clang 19 configure/build checks, or exact + compiler diagnostics for any remaining source incompatibility. diff --git a/extern/argtable3-3.2.2/CMakeLists.txt b/extern/argtable3-3.2.2/CMakeLists.txt new file mode 100644 index 00000000..6b3cad8d --- /dev/null +++ b/extern/argtable3-3.2.2/CMakeLists.txt @@ -0,0 +1,6 @@ +add_library(argtable3 argtable3.c) +target_include_directories( + argtable3 + SYSTEM + PUBLIC ${CMAKE_SOURCE_DIR}/extern/argtable3-3.2.2/ +) diff --git a/extern/argtable3-3.2.2/examples/CMakeLists.txt b/extern/argtable3-3.2.2/examples/CMakeLists.txt index 67e22218..79173bb1 100644 --- a/extern/argtable3-3.2.2/examples/CMakeLists.txt +++ b/extern/argtable3-3.2.2/examples/CMakeLists.txt @@ -29,26 +29,29 @@ ################################################################################ if(ARGTABLE3_ENABLE_ARG_REX_DEBUG) - add_definitions(-DARG_REX_DEBUG) + add_definitions(-DARG_REX_DEBUG) endif() if(NOT ARGTABLE3_REPLACE_GETOPT) - add_definitions(-DARG_REPLACE_GETOPT=0) + add_definitions(-DARG_REPLACE_GETOPT=0) endif() if(ARGTABLE3_LONG_ONLY) - add_definitions(-DARG_LONG_ONLY) + add_definitions(-DARG_LONG_ONLY) endif() file(GLOB EXAMPLES_SOURCES RELATIVE ${PROJECT_SOURCE_DIR}/examples *.c) if(UNIX) - set(ARGTABLE3_EXTRA_LIBS m) + set(ARGTABLE3_EXTRA_LIBS m) endif() foreach(examples_src ${EXAMPLES_SOURCES}) - string(REPLACE ".c" "" examplename ${examples_src}) - add_executable(${examplename} ${PROJECT_SOURCE_DIR}/examples/${examples_src}) - target_include_directories(${examplename} PRIVATE ${PROJECT_SOURCE_DIR}/src) - target_link_libraries(${examplename} argtable3 ${ARGTABLE3_EXTRA_LIBS}) + string(REPLACE ".c" "" examplename ${examples_src}) + add_executable( + ${examplename} + ${PROJECT_SOURCE_DIR}/examples/${examples_src} + ) + target_include_directories(${examplename} PRIVATE ${PROJECT_SOURCE_DIR}/src) + target_link_libraries(${examplename} argtable3 ${ARGTABLE3_EXTRA_LIBS}) endforeach() diff --git a/extern/argtable3-3.2.2/tests/CMakeLists.txt b/extern/argtable3-3.2.2/tests/CMakeLists.txt index 97c6b593..4bfaf53d 100644 --- a/extern/argtable3-3.2.2/tests/CMakeLists.txt +++ b/extern/argtable3-3.2.2/tests/CMakeLists.txt @@ -29,75 +29,81 @@ ################################################################################ if(ARGTABLE3_ENABLE_ARG_REX_DEBUG) - add_definitions(-DARG_REX_DEBUG) + add_definitions(-DARG_REX_DEBUG) endif() if(NOT ARGTABLE3_REPLACE_GETOPT) - add_definitions(-DARG_REPLACE_GETOPT=0) + add_definitions(-DARG_REPLACE_GETOPT=0) endif() if(ARGTABLE3_LONG_ONLY) - add_definitions(-DARG_LONG_ONLY) + add_definitions(-DARG_LONG_ONLY) endif() set(TEST_PUBLIC_SRC_FILES - testall.c - testarglit.c - testargstr.c - testargint.c - testargdate.c - testargdbl.c - testargfile.c - testargrex.c - testargdstr.c - testargcmd.c - CuTest.c + testall.c + testarglit.c + testargstr.c + testargint.c + testargdate.c + testargdbl.c + testargfile.c + testargrex.c + testargdstr.c + testargcmd.c + CuTest.c ) -set(TEST_SRC_FILES - ${TEST_PUBLIC_SRC_FILES} - testarghashtable.c -) +set(TEST_SRC_FILES ${TEST_PUBLIC_SRC_FILES} testarghashtable.c) if(UNIX) - set(ARGTABLE3_EXTRA_LIBS m) + set(ARGTABLE3_EXTRA_LIBS m) endif() if(BUILD_SHARED_LIBS) - add_executable(test_shared ${TEST_PUBLIC_SRC_FILES}) - target_compile_definitions(test_shared PRIVATE -DARGTABLE3_TEST_PUBLIC_ONLY) - target_include_directories(test_shared PRIVATE ${PROJECT_SOURCE_DIR}/src) - target_link_libraries(test_shared argtable3 ${ARGTABLE3_EXTRA_LIBS}) - add_custom_command(TARGET test_shared POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "$" - "$" - ) + add_executable(test_shared ${TEST_PUBLIC_SRC_FILES}) + target_compile_definitions(test_shared PRIVATE -DARGTABLE3_TEST_PUBLIC_ONLY) + target_include_directories(test_shared PRIVATE ${PROJECT_SOURCE_DIR}/src) + target_link_libraries(test_shared argtable3 ${ARGTABLE3_EXTRA_LIBS}) + add_custom_command( + TARGET test_shared + POST_BUILD + COMMAND + ${CMAKE_COMMAND} -E copy_if_different "$" + "$" + ) - add_test(NAME test_shared COMMAND "$") + add_test(NAME test_shared COMMAND "$") else() - add_executable(test_static ${TEST_SRC_FILES}) - target_include_directories(test_static PRIVATE ${PROJECT_SOURCE_DIR}/src) - target_link_libraries(test_static argtable3 ${ARGTABLE3_EXTRA_LIBS}) + add_executable(test_static ${TEST_SRC_FILES}) + target_include_directories(test_static PRIVATE ${PROJECT_SOURCE_DIR}/src) + target_link_libraries(test_static argtable3 ${ARGTABLE3_EXTRA_LIBS}) - add_test(NAME test_static COMMAND "$") + add_test(NAME test_static COMMAND "$") endif() add_executable(test_src ${TEST_SRC_FILES} ${ARGTABLE3_SRC_FILES}) target_include_directories(test_src PRIVATE ${PROJECT_SOURCE_DIR}/src) target_link_libraries(test_src ${ARGTABLE3_EXTRA_LIBS}) -add_custom_command(OUTPUT ${ARGTABLE3_AMALGAMATION_SRC_FILE} - COMMAND "${PROJECT_SOURCE_DIR}/tools/build" dist - WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/tools" +add_custom_command( + OUTPUT ${ARGTABLE3_AMALGAMATION_SRC_FILE} + COMMAND "${PROJECT_SOURCE_DIR}/tools/build" dist + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/tools" ) -add_executable(test_amalgamation ${TEST_SRC_FILES} ${ARGTABLE3_AMALGAMATION_SRC_FILE}) +add_executable( + test_amalgamation + ${TEST_SRC_FILES} + ${ARGTABLE3_AMALGAMATION_SRC_FILE} +) target_include_directories(test_amalgamation PRIVATE ${PROJECT_SOURCE_DIR}/src) target_link_libraries(test_amalgamation ${ARGTABLE3_EXTRA_LIBS}) -add_custom_command(TARGET test_amalgamation PRE_BUILD - COMMAND "${PROJECT_SOURCE_DIR}/tools/build" dist - WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/tools" +add_custom_command( + TARGET test_amalgamation + PRE_BUILD + COMMAND "${PROJECT_SOURCE_DIR}/tools/build" dist + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/tools" ) add_test(NAME test_src COMMAND "$") diff --git a/extern/caliper b/extern/caliper new file mode 160000 index 00000000..063c3974 --- /dev/null +++ b/extern/caliper @@ -0,0 +1 @@ +Subproject commit 063c39746b2f1c498b65c0752cb940dcf9bd7697 diff --git a/interface/kaHIP_interface.cpp b/interface/kaHIP_interface.cpp index 194ea4ff..41cd2ac6 100644 --- a/interface/kaHIP_interface.cpp +++ b/interface/kaHIP_interface.cpp @@ -5,8 +5,15 @@ * *****************************************************************************/ +#include +#include +#include +#include #include -#include +#include +#include +#include +#include #ifdef USEMETIS #include "metis.h" @@ -42,6 +49,67 @@ int kahip_sizeof_idx() { using namespace std; +namespace { +class discard_stream_buffer final : public std::streambuf { + protected: + int_type overflow(int_type character) override { + return traits_type::not_eof(character); + } +}; + +class scoped_output_suppression final { +public: + explicit scoped_output_suppression(bool suppress_output) { + if(suppress_output) { + previous_ = std::cout.rdbuf(&sink_); + } + } + + ~scoped_output_suppression() noexcept { + if(previous_ != nullptr) { + try { + std::cout.rdbuf(previous_); + } catch(...) { + // Restoration cannot replace the active failure. + } + } + } + + scoped_output_suppression(scoped_output_suppression const&) = delete; + scoped_output_suppression& operator=(scoped_output_suppression const&) = delete; + +private: + discard_stream_buffer sink_; + std::streambuf* previous_ = nullptr; +}; + +void write_serial_diagnostic(std::string_view operation, + std::string_view detail) noexcept { + static constexpr auto prefix = std::string_view{"KaHIP serial C boundary "}; + static constexpr auto separator = std::string_view{": "}; + static constexpr auto newline = std::string_view{"\n"}; + static_cast(std::fwrite(prefix.data(), 1, prefix.size(), stderr)); + static_cast(std::fwrite(operation.data(), 1, operation.size(), stderr)); + static_cast(std::fwrite(separator.data(), 1, separator.size(), stderr)); + static_cast(std::fwrite(detail.data(), 1, detail.size(), stderr)); + static_cast(std::fwrite(newline.data(), 1, newline.size(), stderr)); + static_cast(std::fflush(stderr)); +} + +[[noreturn]] void abort_serial_boundary(std::string_view operation, + std::exception_ptr failure) noexcept { + try { + std::rethrow_exception(failure); + } catch(std::exception const& error) { + write_serial_diagnostic(operation, error.what()); + } catch(...) { + write_serial_diagnostic(operation, + "unknown unrecoverable exception"); + } + std::abort(); +} +} // namespace + void internal_kaffpa_set_configuration( configuration & cfg, PartitionConfig & partition_config, int mode) { @@ -111,13 +179,7 @@ void internal_kaffpa_call(PartitionConfig & partition_config, bool perfectly_balance, kahip_idx* edgecut, int* part) { - - //streambuf* backup = cout.rdbuf(); - //ofstream ofs; - //ofs.open("/dev/null"); - //if(suppress_output) { - //cout.rdbuf(ofs.rdbuf()); - //} + scoped_output_suppression output_suppression(suppress_output); partition_config.imbalance = 100*(*imbalance); partition_config.kaffpa_perfectly_balance = perfectly_balance; @@ -139,18 +201,16 @@ void internal_kaffpa_call(PartitionConfig & partition_config, } + quality_metrics qm; + auto const computed_edgecut = qm.edge_cut(G); + forall_nodes(G, node) { part[node] = G.getPartitionIndex(node); } endfor - - quality_metrics qm; - *edgecut = qm.edge_cut(G); - - //ofs.close(); - //cout.rdbuf(backup); + *edgecut = computed_edgecut; } -void kaffpa(int* n, +static void kaffpa_impl(int* n, int* vwgt, kahip_idx* xadj, kahip_idx* adjcwgt, @@ -162,6 +222,7 @@ void kaffpa(int* n, int mode, kahip_idx* edgecut, int* part) { + scoped_output_suppression output_suppression(suppress_output); configuration cfg; PartitionConfig partition_config; partition_config.k = *nparts; @@ -169,10 +230,10 @@ void kaffpa(int* n, internal_kaffpa_set_configuration(cfg, partition_config, mode); partition_config.seed = seed; - internal_kaffpa_call(partition_config, suppress_output, n, vwgt, xadj, adjcwgt, adjncy, nparts, imbalance, false, edgecut, part); + internal_kaffpa_call(partition_config, false, n, vwgt, xadj, adjcwgt, adjncy, nparts, imbalance, false, edgecut, part); } -void kaffpa_balance(int* n, +static void kaffpa_balance_impl(int* n, int* vwgt, kahip_idx* xadj, kahip_idx* adjcwgt, @@ -185,6 +246,7 @@ void kaffpa_balance(int* n, int mode, kahip_idx* edgecut, int* part) { + scoped_output_suppression output_suppression(suppress_output); configuration cfg; PartitionConfig partition_config; partition_config.k = *nparts; @@ -192,10 +254,10 @@ void kaffpa_balance(int* n, internal_kaffpa_set_configuration(cfg, partition_config, mode); partition_config.seed = seed; - internal_kaffpa_call(partition_config, suppress_output, n, vwgt, xadj, adjcwgt, adjncy, nparts, imbalance, perfectly_balance, edgecut, part); + internal_kaffpa_call(partition_config, false, n, vwgt, xadj, adjcwgt, adjncy, nparts, imbalance, perfectly_balance, edgecut, part); } -void kaffpa_balance_NE(int* n, +static void kaffpa_balance_ne_impl(int* n, int* vwgt, kahip_idx* xadj, kahip_idx* adjcwgt, @@ -207,6 +269,7 @@ void kaffpa_balance_NE(int* n, int mode, kahip_idx* edgecut, int* part) { + scoped_output_suppression output_suppression(suppress_output); configuration cfg; PartitionConfig partition_config; partition_config.k = *nparts; @@ -215,7 +278,7 @@ void kaffpa_balance_NE(int* n, partition_config.seed = seed; partition_config.balance_edges = true; - internal_kaffpa_call(partition_config, suppress_output, n, vwgt, xadj, adjcwgt, adjncy, nparts, imbalance, false, edgecut, part); + internal_kaffpa_call(partition_config, false, n, vwgt, xadj, adjcwgt, adjncy, nparts, imbalance, false, edgecut, part); } void internal_nodeseparator_call(PartitionConfig & partition_config, @@ -232,12 +295,7 @@ void internal_nodeseparator_call(PartitionConfig & partition_config, int** separator) { //first perform std partitioning using KaFFPa - streambuf* backup = cout.rdbuf(); - ofstream ofs; - ofs.open("/dev/null"); - if(suppress_output) { - cout.rdbuf(ofs.rdbuf()); - } + scoped_output_suppression output_suppression(suppress_output); partition_config.k = *nparts; partition_config.imbalance = 100*(*imbalance); @@ -250,6 +308,9 @@ void internal_nodeseparator_call(PartitionConfig & partition_config, area_bfs::m_deepth[node] = 0; } endfor + auto computed_count = 0; + auto computed_separator = std::unique_ptr{}; + if( partition_config.k > 2 ) { partitioner.perform_partitioning(partition_config, G); @@ -261,11 +322,10 @@ void internal_nodeseparator_call(PartitionConfig & partition_config, std::vector internal_separator; vsa.compute_vertex_separator(partition_config, G, boundary, internal_separator); - // copy to output variables - *num_nodeseparator_vertices = internal_separator.size(); - *separator = new int[*num_nodeseparator_vertices]; + computed_count = static_cast(internal_separator.size()); + computed_separator = std::make_unique(computed_count); for( unsigned int i = 0; i < internal_separator.size(); i++) { - (*separator)[i] = internal_separator[i]; + computed_separator[i] = internal_separator[i]; } } else { @@ -304,23 +364,23 @@ void internal_nodeseparator_call(PartitionConfig & partition_config, ns_size++; } } endfor - *num_nodeseparator_vertices = ns_size; - *separator = new int[*num_nodeseparator_vertices]; + computed_count = static_cast(ns_size); + computed_separator = std::make_unique(computed_count); unsigned int i = 0; forall_nodes(G, node) { if(G.getPartitionIndex(node) == G.getSeparatorBlock()) { - (*separator)[i] = node; + computed_separator[i] = node; i++; } } endfor } - ofs.close(); - cout.rdbuf(backup); + *num_nodeseparator_vertices = computed_count; + *separator = computed_separator.release(); } -void node_separator(int* n, +static void node_separator_impl(int* n, int* vwgt, kahip_idx* xadj, kahip_idx* adjcwgt, @@ -332,6 +392,7 @@ void node_separator(int* n, int mode, int* num_separator_vertices, int** separator) { + scoped_output_suppression output_suppression(suppress_output); configuration cfg; PartitionConfig partition_config; partition_config.k = *nparts; @@ -361,20 +422,17 @@ void node_separator(int* n, } partition_config.seed = seed; - internal_nodeseparator_call(partition_config, suppress_output, n, vwgt, xadj, adjcwgt, adjncy, nparts, imbalance, mode, num_separator_vertices, separator); + internal_nodeseparator_call(partition_config, false, n, vwgt, xadj, adjcwgt, adjncy, nparts, imbalance, mode, num_separator_vertices, separator); } -void reduced_nd(int* n, +static void reduced_nd_impl(int* n, kahip_idx* xadj, kahip_idx* adjncy, bool suppress_output, int seed, int mode, int* ordering) { - std::streambuf* backup = std::cout.rdbuf(); - if(suppress_output) { - std::cout.rdbuf(nullptr); - } + scoped_output_suppression output_suppression(suppress_output); configuration cfg; PartitionConfig partition_config; @@ -429,22 +487,16 @@ void reduced_nd(int* n, for (int i = 0; i < *n; ++i) { ordering[i] = dissection.ordering()[i]; } - - // Restore cout output stream - std::cout.rdbuf(backup); } #ifdef USEMETIS -void reduced_nd_fast(int* n, +static void reduced_nd_fast_impl(int* n, kahip_idx* xadj, kahip_idx* adjncy, bool suppress_output, int seed, int* ordering) { - std::streambuf* backup = std::cout.rdbuf(); - if(suppress_output) { - std::cout.rdbuf(nullptr); - } + scoped_output_suppression output_suppression(suppress_output); configuration cfg; PartitionConfig partition_config; @@ -514,10 +566,6 @@ void reduced_nd_fast(int* n, for (int i = 0; i < *n; ++i) { ordering[i] = final_labels[i]; } - - // Restore cout output stream - std::cout.rdbuf(backup); - // Delete temporary graph delete[] m_xadj; delete[] m_adjncy; @@ -540,13 +588,7 @@ void internal_processmapping_call(PartitionConfig & partition_config, kahip_idx* edgecut, int* qap, int* part) { - - //streambuf* backup = cout.rdbuf(); - //ofstream ofs; - //ofs.open("/dev/null"); - //if(suppress_output) { - //cout.rdbuf(ofs.rdbuf()); - //} + scoped_output_suppression output_suppression(suppress_output); partition_config.imbalance = 100*(*imbalance); graph_access G; @@ -559,12 +601,8 @@ void internal_processmapping_call(PartitionConfig & partition_config, partitioner.perform_partitioning_krec_hierarchy(partition_config, G); } - forall_nodes(G, node) { - part[node] = G.getPartitionIndex(node); - } endfor - quality_metrics qm; - *edgecut = qm.edge_cut(G); + auto const computed_edgecut = qm.edge_cut(G); int internal_qap = 0; //check if k is a power of 2 @@ -607,20 +645,18 @@ void internal_processmapping_call(PartitionConfig & partition_config, part[node] = G.getPartitionIndex(node); } endfor + *edgecut = computed_edgecut; *qap = (int)internal_qap; - - //ofs.close(); - //cout.rdbuf(backup); } -void process_mapping(int* n, int* vwgt, kahip_idx* xadj, +static void process_mapping_impl(int* n, int* vwgt, kahip_idx* xadj, kahip_idx* adjcwgt, kahip_idx* adjncy, int* hierarchy_parameter, int* distance_parameter, int hierarchy_depth, int mode_partitioning, int mode_mapping, double* imbalance, bool suppress_output, int seed, kahip_idx* edgecut, int* qap, int* part) { - + scoped_output_suppression output_suppression(suppress_output); configuration cfg; PartitionConfig partition_config; partition_config.k = 1; @@ -662,14 +698,15 @@ void process_mapping(int* n, int* vwgt, kahip_idx* xadj, } partition_config.seed = seed; - internal_processmapping_call(partition_config, suppress_output, n, vwgt, xadj, adjcwgt, adjncy, mode_mapping, imbalance, edgecut, qap, part); + internal_processmapping_call(partition_config, false, n, vwgt, xadj, adjcwgt, adjncy, mode_mapping, imbalance, edgecut, qap, part); }; -void edge_partitioning(int* n, int* vwgt, kahip_idx* xadj, +static void edge_partitioning_impl(int* n, int* vwgt, kahip_idx* xadj, kahip_idx* adjcwgt, kahip_idx* adjncy, int* nparts, double* imbalance, bool suppress_output, int seed, int mode, int* vertexcut, int* part, kahip_idx infinity_edge_weight) { + scoped_output_suppression output_suppression(suppress_output); configuration cfg; PartitionConfig partition_config; partition_config.k = *nparts; @@ -697,11 +734,128 @@ void edge_partitioning(int* n, int* vwgt, kahip_idx* xadj, splitter.fix_cut_dominant_edges(); std::vector edge_partition = splitter.project_partition(); - *vertexcut = static_cast(splitter.calculate_vertex_cut(edge_partition)); + auto const computed_vertexcut = + static_cast(splitter.calculate_vertex_cut(edge_partition)); for (std::size_t i = 0; i < edge_partition.size(); ++i) { part[i] = edge_partition[i]; } + *vertexcut = computed_vertexcut; +} + +void kaffpa(int* n, int* vwgt, kahip_idx* xadj, kahip_idx* adjcwgt, + kahip_idx* adjncy, int* nparts, double* imbalance, + bool suppress_output, int seed, int mode, kahip_idx* edgecut, + int* part) noexcept { + try { + kaffpa_impl(n, vwgt, xadj, adjcwgt, adjncy, nparts, + imbalance, suppress_output, seed, mode, edgecut, + part); + } catch(...) { + abort_serial_boundary("kaffpa", std::current_exception()); + } +} + +void kaffpa_balance(int* n, int* vwgt, kahip_idx* xadj, + kahip_idx* adjcwgt, kahip_idx* adjncy, int* nparts, + double* imbalance, bool perfectly_balance, + bool suppress_output, int seed, int mode, + kahip_idx* edgecut, int* part) noexcept { + try { + kaffpa_balance_impl( + n, vwgt, xadj, adjcwgt, adjncy, nparts, imbalance, + perfectly_balance, suppress_output, seed, mode, edgecut, + part); + } catch(...) { + abort_serial_boundary("kaffpa_balance", + std::current_exception()); + } } +void kaffpa_balance_NE(int* n, int* vwgt, kahip_idx* xadj, + kahip_idx* adjcwgt, kahip_idx* adjncy, int* nparts, + double* imbalance, bool suppress_output, int seed, + int mode, kahip_idx* edgecut, int* part) noexcept { + try { + kaffpa_balance_ne_impl( + n, vwgt, xadj, adjcwgt, adjncy, nparts, imbalance, + suppress_output, seed, mode, edgecut, part); + } catch(...) { + abort_serial_boundary("kaffpa_balance_NE", + std::current_exception()); + } +} +void process_mapping( + int* n, int* vwgt, kahip_idx* xadj, kahip_idx* adjcwgt, + kahip_idx* adjncy, int* hierarchy_parameter, int* distance_parameter, + int hierarchy_depth, int mode_partitioning, int mode_mapping, + double* imbalance, bool suppress_output, int seed, kahip_idx* edgecut, + int* qap, int* part) noexcept { + try { + process_mapping_impl( + n, vwgt, xadj, adjcwgt, adjncy, hierarchy_parameter, + distance_parameter, hierarchy_depth, mode_partitioning, + mode_mapping, imbalance, suppress_output, seed, edgecut, + qap, part); + } catch(...) { + abort_serial_boundary("process_mapping", + std::current_exception()); + } +} + +void node_separator(int* n, int* vwgt, kahip_idx* xadj, + kahip_idx* adjcwgt, kahip_idx* adjncy, int* nparts, + double* imbalance, bool suppress_output, int seed, + int mode, int* num_separator_vertices, + int** separator) noexcept { + try { + node_separator_impl( + n, vwgt, xadj, adjcwgt, adjncy, nparts, imbalance, + suppress_output, seed, mode, num_separator_vertices, + separator); + } catch(...) { + abort_serial_boundary("node_separator", + std::current_exception()); + } +} + +void reduced_nd(int* n, kahip_idx* xadj, kahip_idx* adjncy, + bool suppress_output, int seed, int mode, + int* ordering) noexcept { + try { + reduced_nd_impl(n, xadj, adjncy, suppress_output, seed, mode, + ordering); + } catch(...) { + abort_serial_boundary("reduced_nd", std::current_exception()); + } +} + +#ifdef USEMETIS +void reduced_nd_fast(int* n, kahip_idx* xadj, kahip_idx* adjncy, + bool suppress_output, int seed, int* ordering) noexcept { + try { + reduced_nd_fast_impl(n, xadj, adjncy, suppress_output, seed, + ordering); + } catch(...) { + abort_serial_boundary("reduced_nd_fast", + std::current_exception()); + } +} +#endif + +void edge_partitioning( + int* n, int* vwgt, kahip_idx* xadj, kahip_idx* adjcwgt, + kahip_idx* adjncy, int* nparts, double* imbalance, + bool suppress_output, int seed, int mode, int* vertexcut, int* part, + kahip_idx infinity_edge_weight) noexcept { + try { + edge_partitioning_impl( + n, vwgt, xadj, adjcwgt, adjncy, nparts, imbalance, + suppress_output, seed, mode, vertexcut, part, + infinity_edge_weight); + } catch(...) { + abort_serial_boundary("edge_partitioning", + std::current_exception()); + } +} diff --git a/interface/kaHIP_interface.h b/interface/kaHIP_interface.h index 2a68f549..4366ec41 100644 --- a/interface/kaHIP_interface.h +++ b/interface/kaHIP_interface.h @@ -10,6 +10,10 @@ #include +#ifndef __cplusplus +#include +#endif + #ifdef KAHIP_64BIT typedef int64_t kahip_idx; #else @@ -17,23 +21,60 @@ typedef int32_t kahip_idx; #endif #ifdef __cplusplus - -extern "C" -{ +inline constexpr int FAST = 0; +inline constexpr int ECO = 1; +inline constexpr int STRONG = 2; +inline constexpr int KAHIP_FASTSOCIAL = 3; +inline constexpr int KAHIP_ECOSOCIAL = 4; +#else +enum { + FAST = 0, + ECO = 1, + STRONG = 2, + KAHIP_FASTSOCIAL = 3, + KAHIP_ECOSOCIAL = 4 +}; #endif -// returns the size of kahip_idx in bytes (4 for 32-bit, 8 for 64-bit) -int kahip_sizeof_idx(); +/* + * KaHIP and ParHIP historically exposed different values under the same two + * unprefixed names. Keep those source-compatible aliases when either header + * is used alone. A translation unit that includes both interfaces must use + * KAHIP_* or PARHIP_* for the two ambiguous social modes; the first included + * header retains the legacy aliases. + */ +#ifndef KAHIP_LEGACY_SOCIAL_MODE_NAMES_DEFINED +#define KAHIP_LEGACY_SOCIAL_MODE_NAMES_DEFINED +#ifdef __cplusplus +inline constexpr int FASTSOCIAL = KAHIP_FASTSOCIAL; +inline constexpr int ECOSOCIAL = KAHIP_ECOSOCIAL; +#else +enum { FASTSOCIAL = KAHIP_FASTSOCIAL, ECOSOCIAL = KAHIP_ECOSOCIAL }; +#endif +#endif +#ifdef __cplusplus +inline constexpr int STRONGSOCIAL = 5; +inline constexpr int MAPMODE_MULTISECTION = 0; +inline constexpr int MAPMODE_BISECTION = 1; +#else +enum { + STRONGSOCIAL = 5, + MAPMODE_MULTISECTION = 0, + MAPMODE_BISECTION = 1 +}; +#endif -const int FAST = 0; -const int ECO = 1; -const int STRONG = 2; -const int FASTSOCIAL = 3; -const int ECOSOCIAL = 4; -const int STRONGSOCIAL = 5; +#ifdef __cplusplus +extern "C" { +#define KAHIP_DEFAULT_ARGUMENT(value) = value +#define KAHIP_NOEXCEPT noexcept +#else +#define KAHIP_DEFAULT_ARGUMENT(value) +#define KAHIP_NOEXCEPT +#endif -const int MAPMODE_MULTISECTION = 0; -const int MAPMODE_BISECTION = 1; +// returns the size of kahip_idx in bytes (4 for 32-bit, 8 for 64-bit) +int kahip_sizeof_idx(void); // same data structures as in metis // edgecut and part are output parameters @@ -41,7 +82,7 @@ const int MAPMODE_BISECTION = 1; void kaffpa(int* n, int* vwgt, kahip_idx* xadj, kahip_idx* adjcwgt, kahip_idx* adjncy, int* nparts, double* imbalance, bool suppress_output, int seed, int mode, - kahip_idx* edgecut, int* part); + kahip_idx* edgecut, int* part) KAHIP_NOEXCEPT; // same as kaffpa, provides an additional parameter for perfect balance void kaffpa_balance(int* n, int* vwgt, kahip_idx* xadj, @@ -49,13 +90,13 @@ void kaffpa_balance(int* n, int* vwgt, kahip_idx* xadj, double* imbalance, bool perfectly_balance, bool suppress_output, int seed, int mode, - kahip_idx* edgecut, int* part); + kahip_idx* edgecut, int* part) KAHIP_NOEXCEPT; // balance constraint on nodes and edges void kaffpa_balance_NE(int* n, int* vwgt, kahip_idx* xadj, kahip_idx* adjcwgt, kahip_idx* adjncy, int* nparts, double* imbalance, bool suppress_output, int seed, int mode, - kahip_idx* edgecut, int* part); + kahip_idx* edgecut, int* part) KAHIP_NOEXCEPT; // same data structures as in metis // edgecut and part and qap are output parameters @@ -66,33 +107,40 @@ void process_mapping(int* n, int* vwgt, kahip_idx* xadj, int mode_partitioning, int mode_mapping, double* imbalance, bool suppress_output, int seed, - kahip_idx* edgecut, int* qap, int* part); + kahip_idx* edgecut, int* qap, int* part) KAHIP_NOEXCEPT; void node_separator(int* n, int* vwgt, kahip_idx* xadj, kahip_idx* adjcwgt, kahip_idx* adjncy, int* nparts, double* imbalance, bool suppress_output, int seed, int mode, - int* num_separator_vertices, int** separator); + int* num_separator_vertices, int** separator) + KAHIP_NOEXCEPT; // takes an unweighted graph and performs reduced nested dissection // ordering is the output parameter, an array of n ints void reduced_nd(int* n, kahip_idx* xadj, kahip_idx* adjncy, bool suppress_output, int seed, int mode, - int* ordering); + int* ordering) KAHIP_NOEXCEPT; void edge_partitioning(int* n, int* vwgt, kahip_idx* xadj, kahip_idx* adjcwgt, kahip_idx* adjncy, int* nparts, double* imbalance, bool suppress_output, int seed, int mode, - int* vertexcut, int* part, kahip_idx infinity_edge_weight = 1000); + int* vertexcut, int* part, + kahip_idx infinity_edge_weight KAHIP_DEFAULT_ARGUMENT(1000)) + KAHIP_NOEXCEPT; #ifdef USEMETIS // reduced nested dissection with metis void reduced_nd_fast(int* n, kahip_idx* xadj, kahip_idx* adjncy, - bool suppress_output, int seed, int* ordering); + bool suppress_output, int seed, int* ordering) + KAHIP_NOEXCEPT; #endif #ifdef __cplusplus } #endif +#undef KAHIP_DEFAULT_ARGUMENT +#undef KAHIP_NOEXCEPT + #endif /* end of include guard: KAFFPA_INTERFACE_RYEEZ6WJ */ diff --git a/lib/kahip.pc.in b/lib/kahip.pc.in index b44d0145..ed8699c4 100644 --- a/lib/kahip.pc.in +++ b/lib/kahip.pc.in @@ -2,7 +2,7 @@ # Copyright (c) 2019 # MIT license (https://opensource.org/license/mit) -prefix=@CMAKE_INSTALL_PREFIX@ +prefix=${pcfiledir}/@KAHIP_PKGCONFIG_PREFIX_FROM_PCFILEDIR@ exec_prefix=${prefix} includedir=${exec_prefix}/@CMAKE_INSTALL_INCLUDEDIR@ libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ @@ -12,4 +12,4 @@ Description: The graph partitioning framework KaHIP URL: https://kahip.github.io/ Version: @PROJECT_VERSION@ Libs: -L${libdir} -lkahip -Cflags: -I${includedir} +Cflags: -I${includedir} @KAHIP_PKGCONFIG_CFLAGS@ diff --git a/lib/parallel_mh/evolutionary_collectives.h b/lib/parallel_mh/evolutionary_collectives.h new file mode 100644 index 00000000..7e40b3c3 --- /dev/null +++ b/lib/parallel_mh/evolutionary_collectives.h @@ -0,0 +1,388 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tools/fatal_diagnostics.h" + +namespace kahip::parallel_mh { +struct evolutionary_broadcast_options final { + std::size_t mpi3_round_ceiling = + static_cast(std::numeric_limits::max()); + bool force_mpi3 = false; +}; + +namespace detail { +[[nodiscard]] inline auto active_rank(MPI_Comm communicator) noexcept -> int { + auto rank = -1; + if (communicator != MPI_COMM_NULL && + PMPI_Comm_rank(communicator, &rank) == MPI_SUCCESS) { + return rank; + } + return -1; +} + +template +using unqualified_t = std::remove_cv_t>; + +template +concept evolutionary_mpi_scalar = + std::same_as, int> || + std::same_as, unsigned> || + std::same_as, long> || + std::same_as, unsigned long> || + std::same_as, long long> || + std::same_as, unsigned long long>; + +template +[[nodiscard]] auto native_datatype() noexcept -> MPI_Datatype { + using value_type = unqualified_t; + if constexpr (std::same_as) { + return MPI_INT; + } else if constexpr (std::same_as) { + return MPI_UNSIGNED; + } else if constexpr (std::same_as) { + return MPI_LONG; + } else if constexpr (std::same_as) { + return MPI_UNSIGNED_LONG; + } else if constexpr (std::same_as) { + return MPI_LONG_LONG_INT; + } else { + return MPI_UNSIGNED_LONG_LONG; + } +} + +[[noreturn]] inline void abort_evolutionary_collective( + MPI_Comm communicator, + std::string_view operation, + std::string_view diagnostic) noexcept { + auto const rank = active_rank(communicator); + if (rank >= 0) { + kahip::diagnostics::critical( + "MPI evolutionary collective failure in ", operation, " on rank ", + rank, ": ", diagnostic); + } else { + kahip::diagnostics::critical( + "MPI evolutionary collective failure in ", operation, ": ", + diagnostic); + } + static_cast(MPI_Abort(communicator, EXIT_FAILURE)); + std::abort(); +} + +[[noreturn]] inline void abort_evolutionary_lifecycle( + std::string_view diagnostic) noexcept { + kahip::diagnostics::critical("MPI evolutionary lifecycle failure: ", + diagnostic); + std::abort(); +} + +[[noreturn]] inline void abort_evolutionary_mpi_error( + MPI_Comm communicator, + int error_code, + std::string_view operation) noexcept { + auto const rank = active_rank(communicator); + if (rank >= 0) { + kahip::diagnostics::critical( + "MPI backend failure: ", operation, " returned raw error ", error_code, + " on rank ", rank); + } else { + kahip::diagnostics::critical( + "MPI backend failure: ", operation, " returned raw error ", + error_code); + } + static_cast(MPI_Abort(communicator, EXIT_FAILURE)); + std::abort(); +} + +inline void check_mpi(int result, + MPI_Comm communicator, + std::string_view operation) noexcept { + if (result != MPI_SUCCESS) { + abort_evolutionary_mpi_error(communicator, result, operation); + } +} + +[[nodiscard]] inline auto mpi_runtime_is_active() noexcept -> bool { + auto initialized = 0; + auto finalized = 0; + auto const initialized_result = MPI_Initialized(&initialized); + if (initialized_result != MPI_SUCCESS) { + abort_evolutionary_lifecycle("MPI_Initialized failed"); + } + if (initialized == 0) { + return false; + } + auto const finalized_result = MPI_Finalized(&finalized); + if (finalized_result != MPI_SUCCESS) { + abort_evolutionary_lifecycle("MPI_Finalized failed"); + } + return finalized == 0; +} + +[[nodiscard]] inline auto checked_count(std::size_t count, + MPI_Comm communicator, + std::string_view operation) noexcept + -> int { + if (count > static_cast(std::numeric_limits::max())) { + abort_evolutionary_collective( + communicator, operation, + "partition-vector count exceeds the MPI int interface boundary"); + } + return static_cast(count); +} + +template +void broadcast_partition_payload( + MPI_Comm communicator, + T* data, + std::size_t count, + int root, + evolutionary_broadcast_options options) noexcept { + static_assert(sizeof(std::size_t) <= sizeof(std::uint64_t)); + + auto communicator_size = 0; + check_mpi(MPI_Comm_size(communicator, &communicator_size), communicator, + "MPI_Comm_size(evolutionary partition broadcast)"); + + auto const encoded_root = root >= 0 + ? static_cast(root) + : std::numeric_limits::max(); + auto const local_signature = std::array{ + static_cast(count), + static_cast(options.mpi3_round_ceiling), + options.force_mpi3 ? std::uint64_t{1} : std::uint64_t{0}, encoded_root}; + auto minimum_signature = std::array{}; + auto maximum_signature = std::array{}; + check_mpi(MPI_Allreduce(local_signature.data(), minimum_signature.data(), + static_cast(local_signature.size()), + MPI_UINT64_T, MPI_MIN, communicator), + communicator, + "MPI_Allreduce(evolutionary partition signature minimum)"); + check_mpi(MPI_Allreduce(local_signature.data(), maximum_signature.data(), + static_cast(local_signature.size()), + MPI_UINT64_T, MPI_MAX, communicator), + communicator, + "MPI_Allreduce(evolutionary partition signature maximum)"); + + auto const locally_valid = options.mpi3_round_ceiling != 0 && root >= 0 && + root < communicator_size && + (count == 0 || data != nullptr); + auto local_valid = locally_valid ? 1 : 0; + auto all_valid = 0; + check_mpi(MPI_Allreduce(&local_valid, &all_valid, 1, MPI_INT, MPI_MIN, + communicator), + communicator, + "MPI_Allreduce(evolutionary partition signature validity)"); + + if (minimum_signature != maximum_signature) { + abort_evolutionary_collective( + communicator, "MPI_Bcast(evolutionary best partition)", + "partition broadcast arguments differ across communicator"); + } + if (all_valid == 0) { + abort_evolutionary_collective(communicator, + "MPI_Bcast(evolutionary best partition)", + "partition broadcast arguments are invalid"); + } + if (count == 0) { + return; + } + +#if KAHIP_HAVE_MPI_BCAST_C + if (!options.force_mpi3 && std::in_range(count)) { + check_mpi(MPI_Bcast_c(data, static_cast(count), + native_datatype(), root, communicator), + communicator, "MPI_Bcast_c(evolutionary best partition)"); + return; + } +#endif + + auto const ceiling = + std::min(options.mpi3_round_ceiling, + static_cast(std::numeric_limits::max())); + for (std::size_t offset = 0; offset < count;) { + auto const chunk = std::min(ceiling, count - offset); + check_mpi(MPI_Bcast(data + offset, static_cast(chunk), + native_datatype(), root, communicator), + communicator, + "MPI_Bcast(evolutionary best partition MPI-3 round)"); + offset += chunk; + } +} +} // namespace detail + +class owned_evolutionary_communicator final { + public: + explicit owned_evolutionary_communicator(MPI_Comm source) noexcept { + if (!detail::mpi_runtime_is_active()) { + detail::abort_evolutionary_lifecycle( + "communicator ownership requires an active MPI runtime"); + } + if (source == MPI_COMM_NULL) { + detail::abort_evolutionary_collective( + MPI_COMM_WORLD, "MPI_Comm_dup(evolutionary communicator)", + "communicator ownership requires a live intracommunicator"); + } + + auto is_intercommunicator = 0; + detail::check_mpi(MPI_Comm_test_inter(source, &is_intercommunicator), + source, "MPI_Comm_test_inter(evolutionary communicator)"); + if (is_intercommunicator != 0) { + detail::abort_evolutionary_collective( + source, "MPI_Comm_dup(evolutionary communicator)", + "communicator ownership requires an intracommunicator"); + } + + detail::check_mpi(MPI_Comm_dup(source, &communicator_), source, + "MPI_Comm_dup(evolutionary communicator)"); + detail::check_mpi(MPI_Comm_set_errhandler(communicator_, MPI_ERRORS_RETURN), + communicator_, + "MPI_Comm_set_errhandler(evolutionary communicator)"); + } + + ~owned_evolutionary_communicator() noexcept { reset(); } + + owned_evolutionary_communicator(owned_evolutionary_communicator const&) = + delete; + auto operator=(owned_evolutionary_communicator const&) + -> owned_evolutionary_communicator& = delete; + + owned_evolutionary_communicator( + owned_evolutionary_communicator&& other) noexcept + : communicator_(std::exchange(other.communicator_, MPI_COMM_NULL)) {} + + auto operator=(owned_evolutionary_communicator&& other) noexcept + -> owned_evolutionary_communicator& { + if (this != &other) { + reset(); + communicator_ = std::exchange(other.communicator_, MPI_COMM_NULL); + } + return *this; + } + + [[nodiscard]] auto native_handle() const noexcept -> MPI_Comm { + return communicator_; + } + + [[nodiscard]] auto rank() const noexcept -> int { + auto result = -1; + detail::check_mpi(MPI_Comm_rank(communicator_, &result), communicator_, + "MPI_Comm_rank(evolutionary communicator)"); + return result; + } + + [[nodiscard]] auto size() const noexcept -> int { + auto result = 0; + detail::check_mpi(MPI_Comm_size(communicator_, &result), communicator_, + "MPI_Comm_size(evolutionary communicator)"); + return result; + } + + private: + void reset() noexcept { + if (communicator_ == MPI_COMM_NULL) { + return; + } + if (!detail::mpi_runtime_is_active()) { + detail::abort_evolutionary_lifecycle( + "owned communicator outlived the active MPI runtime"); + } + auto communicator = std::exchange(communicator_, MPI_COMM_NULL); + auto const result = MPI_Comm_free(&communicator); + if (result != MPI_SUCCESS) { + detail::abort_evolutionary_mpi_error( + MPI_COMM_WORLD, result, "MPI_Comm_free(evolutionary communicator)"); + } + } + + MPI_Comm communicator_ = MPI_COMM_NULL; +}; + +template +[[nodiscard]] constexpr auto objective_improved(Objective candidate, + Objective incumbent) noexcept + -> bool { + return candidate < incumbent; +} + +inline void broadcast_permutation(MPI_Comm communicator, + std::span permutation, + int root) noexcept { + auto const count = detail::checked_count( + permutation.size(), communicator, "MPI_Bcast(evolutionary permutation)"); + detail::check_mpi( + MPI_Bcast(permutation.data(), count, MPI_UNSIGNED, root, communicator), + communicator, "MPI_Bcast(evolutionary permutation)"); +} + +template +[[nodiscard]] auto select_and_broadcast_best_partition( + MPI_Comm communicator, + EdgeWeight local_objective, + NodeWeight local_max_block_weight, + NodeWeight upper_bound_partition, + PartitionID* local_partition, + std::size_t partition_size, + evolutionary_broadcast_options broadcast_options = {}) noexcept + -> EdgeWeight { + auto rank = -1; + detail::check_mpi(MPI_Comm_rank(communicator, &rank), communicator, + "MPI_Comm_rank(evolutionary best partition)"); + + auto const local_infeasible = + local_max_block_weight > upper_bound_partition ? 1 : 0; + auto all_infeasible = 0; + detail::check_mpi(MPI_Allreduce(&local_infeasible, &all_infeasible, 1, + MPI_INT, MPI_MIN, communicator), + communicator, "MPI_Allreduce(evolutionary feasibility)"); + + auto const eligible = all_infeasible != 0 || local_infeasible == 0; + auto const candidate_objective = + eligible ? local_objective : std::numeric_limits::max(); + auto best_objective = std::numeric_limits::max(); + detail::check_mpi(MPI_Allreduce(&candidate_objective, &best_objective, 1, + detail::native_datatype(), + MPI_MIN, communicator), + communicator, "MPI_Allreduce(evolutionary objective)"); + + auto const candidate_weight = eligible && local_objective == best_objective + ? local_max_block_weight + : std::numeric_limits::max(); + auto best_block_weight = std::numeric_limits::max(); + detail::check_mpi(MPI_Allreduce(&candidate_weight, &best_block_weight, 1, + detail::native_datatype(), + MPI_MIN, communicator), + communicator, + "MPI_Allreduce(evolutionary maximum block weight)"); + + auto const candidate_root = + eligible && local_objective == best_objective && + local_max_block_weight == best_block_weight + ? rank + : std::numeric_limits::max(); + auto selected_root = std::numeric_limits::max(); + detail::check_mpi(MPI_Allreduce(&candidate_root, &selected_root, 1, MPI_INT, + MPI_MIN, communicator), + communicator, + "MPI_Allreduce(evolutionary broadcaster rank)"); + + detail::broadcast_partition_payload(communicator, local_partition, + partition_size, selected_root, + broadcast_options); + return best_objective; +} +} // namespace kahip::parallel_mh diff --git a/lib/parallel_mh/evolutionary_feasibility.h b/lib/parallel_mh/evolutionary_feasibility.h new file mode 100644 index 00000000..73167d02 --- /dev/null +++ b/lib/parallel_mh/evolutionary_feasibility.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "../../parallel/shared/random_state.h" + +namespace kahip::parallel_mh { +template +[[nodiscard]] auto maximum_block_weight(Graph& graph) + -> std::optional { + auto const raw_block_count = graph.get_partition_count(); + if (!std::in_range(raw_block_count) || raw_block_count == 0) { + return std::nullopt; + } + auto const block_count = static_cast(raw_block_count); + auto block_weights = std::vector(block_count, Weight{0}); + + using node_type = decltype(graph.number_of_nodes()); + for (auto node = node_type{0}; node < graph.number_of_nodes(); ++node) { + auto const raw_block = graph.getPartitionIndex(node); + if (!std::in_range(raw_block) || + static_cast(raw_block) >= block_count) { + return std::nullopt; + } + auto const weight = + random_compat::checked_narrow(graph.getNodeWeight(node)); + if (!weight.has_value()) { + return std::nullopt; + } + auto& block_weight = block_weights[static_cast(raw_block)]; + if (!random_compat::checked_add(block_weight, *weight)) { + return std::nullopt; + } + } + + return *std::ranges::max_element(block_weights); +} +} // namespace kahip::parallel_mh diff --git a/lib/parallel_mh/exchange/exchanger.cpp b/lib/parallel_mh/exchange/exchanger.cpp index 3c96ad2c..cd2cae10 100644 --- a/lib/parallel_mh/exchange/exchanger.cpp +++ b/lib/parallel_mh/exchange/exchanger.cpp @@ -1,295 +1,439 @@ /****************************************************************************** - * exchanger.cpp - * * + * exchanger.cpp + * * Source of KaHIP -- Karlsruhe High Quality Partitioning. * Christian Schulz *****************************************************************************/ -#include - #include "exchanger.h" -#include "tools/quality_metrics.h" -#include "tools/random_functions.h" - -exchanger::exchanger(MPI_Comm communicator) { - m_prev_best_objective = std::numeric_limits::max(); - - m_communicator = communicator; - - int rank, comm_size; - MPI_Comm_rank( m_communicator, &rank); - MPI_Comm_size( m_communicator, &comm_size); - - m_cur_num_pushes = 0; - if(comm_size > 2) m_max_num_pushes = ceil(log2(comm_size)); - else m_max_num_pushes = 1; - std::cout << "max num pushes " << m_max_num_pushes << std::endl; +#include - m_allready_send_to.resize(comm_size); +#include +#include +#include +#include +#include +#include +#include - for( unsigned i = 0; i < m_allready_send_to.size(); i++) { - m_allready_send_to[i] = false; - } +#include "parallel_mh/evolutionary_collectives.h" +#include "parallel_mh/population_size_broadcast.h" +#include "tools/quality_metrics.h" +#include "tools/random_functions.h" - m_allready_send_to[rank] = true; +auto exchanger::pending_send::operator=(pending_send&& other) noexcept + -> pending_send& { + if (this != &other) { + if (request != MPI_REQUEST_NULL) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + communicator, "evolutionary pending-send move assignment", + "live MPI request would lose its completion ownership"); + } + payload = std::move(other.payload); + request = std::exchange(other.request, MPI_REQUEST_NULL); + communicator = std::exchange(other.communicator, MPI_COMM_NULL); + } + return *this; } -exchanger::~exchanger() { - MPI_Barrier( m_communicator ); - int rank; - MPI_Comm_rank( m_communicator, &rank); - - int flag; MPI_Status st; - MPI_Iprobe(MPI_ANY_SOURCE, MPI_ANY_TAG, m_communicator, &flag, &st); - - while(flag) { - int message_length; - MPI_Get_count(&st, MPI_INT, &message_length); - - int* partition_map = new int[message_length]; - MPI_Status rst; - MPI_Recv( partition_map, message_length, MPI_INT, st.MPI_SOURCE, rank, m_communicator, &rst); - - delete[] partition_map; - MPI_Iprobe(MPI_ANY_SOURCE, MPI_ANY_TAG, m_communicator, &flag, &st); - } - - MPI_Barrier( m_communicator ); - for( unsigned i = 0; i < m_request_pointers.size(); i++) { - MPI_Cancel( m_request_pointers[i] ); - } - - for( unsigned i = 0; i < m_request_pointers.size(); i++) { - MPI_Status st; - MPI_Wait( m_request_pointers[i], & st ); - delete[] m_partition_map_buffers[i]; - delete m_request_pointers[i]; - } - +exchanger::exchanger(MPI_Comm communicator) + : m_prev_best_objective(std::numeric_limits::max()), + m_max_num_pushes(1), + m_rank(-1), + m_size(0), + m_communicator(communicator) { + if (!::kahip::parallel_mh::detail::mpi_runtime_is_active()) { + ::kahip::parallel_mh::detail::abort_evolutionary_lifecycle( + "evolutionary exchange requires an active MPI runtime"); + } + if (m_communicator == MPI_COMM_NULL) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + MPI_COMM_WORLD, "evolutionary exchange construction", + "exchange requires a live intracommunicator"); + } + + auto is_intercommunicator = 0; + ::kahip::parallel_mh::detail::check_mpi( + MPI_Comm_test_inter(m_communicator, &is_intercommunicator), + m_communicator, "MPI_Comm_test_inter(evolutionary exchange)"); + if (is_intercommunicator != 0) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, "evolutionary exchange construction", + "exchange requires an intracommunicator"); + } + + ::kahip::parallel_mh::detail::check_mpi( + MPI_Comm_rank(m_communicator, &m_rank), m_communicator, + "MPI_Comm_rank(evolutionary exchange)"); + ::kahip::parallel_mh::detail::check_mpi( + MPI_Comm_size(m_communicator, &m_size), m_communicator, + "MPI_Comm_size(evolutionary exchange)"); + if (m_rank < 0 || m_rank >= m_size || m_size <= 0) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, "evolutionary exchange construction", + "MPI returned an invalid evolutionary communicator rank or size"); + } + + m_max_num_pushes = + m_size > 2 ? static_cast(std::ceil(std::log2(m_size))) : 1; + std::cout << "max num pushes " << m_max_num_pushes << std::endl; + m_already_sent_to.assign(static_cast(m_size), false); + m_already_sent_to[static_cast(m_rank)] = true; + m_issued_sends.assign(static_cast(m_size), 0); + m_consumed_receives.assign(static_cast(m_size), 0); } -void exchanger::diversify_population( PartitionConfig & config, graph_access & G, population & island, bool replace ) { - - int rank, comm_size; - MPI_Comm_rank( m_communicator, &rank); - MPI_Comm_size( m_communicator, &comm_size); - - std::vector permutation(comm_size, 0); - - if( rank == ROOT ) { - random_functions::circular_permutation(permutation); - } - - MPI_Bcast(&permutation[0], comm_size, MPI_INT, ROOT, m_communicator); - - int from = 0; - int to = permutation[rank]; - for( unsigned i = 0; i < permutation.size(); i++) { - if( permutation[i] == (unsigned)rank ) { - from = (int)i; - break; - } - } - - Individuum in; - Individuum out; - - if(config.mh_diversify_best) { - island.get_best_individuum(in); - } else { - island.get_random_individuum(in); - } - exchange_individum( config, G, from, rank, to, in, out); - - if( replace ) { - island.replace( in, out ); - } else { - island.insert( G, out ); - } - +exchanger::~exchanger() noexcept { + if (!m_finished) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, "evolutionary rumor exchange teardown", + "exchanger destroyed before explicit finish drained all messages"); + } } -void exchanger::quick_start( PartitionConfig & config, graph_access & G, population & island ) { - int comm_size; - MPI_Comm_size( m_communicator, &comm_size); - - unsigned no_of_individuals = ceil(config.mh_pool_size / (double)comm_size) - 1; - - std::cout << "creating " << no_of_individuals << std::endl; - - for(unsigned i = 0; i < no_of_individuals; i++) { - PartitionConfig copy = config; - copy.combine = false; - copy.graph_allready_partitioned = false; - - Individuum ind; - island.createIndividuum(config, G, ind, true); - island.insert(G, ind); - } - - int reps = config.mh_pool_size - no_of_individuals; - if(reps < 0) reps = 0; - - PartitionConfig div_config = config; - div_config.mh_diversify_best = false; - for( unsigned i = 0; i < (unsigned) reps; i++) { - diversify_population( div_config , G, island, false); - } +auto exchanger::observe_graph_order(std::size_t graph_order, + std::string_view operation) -> int { + if (m_finished) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, operation, + "evolutionary exchange used after explicit finish"); + } + if (m_graph_order_observed && graph_order != m_graph_order) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, operation, + "evolutionary exchange graph order changed during its lifetime"); + } + m_graph_order = graph_order; + m_graph_order_observed = true; + return ::kahip::parallel_mh::detail::checked_count(graph_order, + m_communicator, operation); } +void exchanger::validate_partition_status(MPI_Status const& status, + int expected_source, + int expected_tag, + int expected_count, + std::string_view operation) const { + if (status.MPI_SOURCE != expected_source || expected_source < 0 || + expected_source >= m_size) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, operation, + "rumor message source does not match the requested peer"); + } + if (status.MPI_TAG != expected_tag) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, operation, + "rumor message tag does not match receiver rank"); + } + auto received_count = 0; + ::kahip::parallel_mh::detail::check_mpi( + MPI_Get_count(&status, MPI_INT, &received_count), m_communicator, + "MPI_Get_count(evolutionary partition payload)"); + if (received_count != expected_count) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, operation, + "rumor message count does not match the graph order"); + } +} -void exchanger::exchange_individum( const PartitionConfig & config, graph_access & G, - int & from, int & rank, int & to, - Individuum & in, Individuum & out) { - //recv. edge cut, partition_map, cut_edges from "from" - //send in to "to" - - int* partition_map = new int[G.number_of_nodes()]; - out.partition_map = partition_map; - out.cut_edges = new std::vector(); +void exchanger::diversify_population(PartitionConfig& config, + graph_access& graph, + population& island, + bool replace) { + static_cast( + observe_graph_order(static_cast(graph.number_of_nodes()), + "MPI_Sendrecv(evolutionary permutation exchange)")); + auto permutation = std::vector(static_cast(m_size), 0); + if (m_rank == ROOT) { + random_functions::circular_permutation(permutation); + } + ::kahip::parallel_mh::broadcast_permutation(m_communicator, permutation, + ROOT); + + auto canonical = permutation; + std::ranges::sort(canonical); + auto expected = std::vector(canonical.size()); + std::iota(expected.begin(), expected.end(), 0U); + if (canonical != expected) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, "MPI_Bcast(evolutionary permutation)", + "evolutionary permutation is not a bijection of communicator ranks"); + } + + auto const destination = + static_cast(permutation[static_cast(m_rank)]); + auto const source_position = + std::ranges::find(permutation, static_cast(m_rank)); + auto const source = + static_cast(std::distance(permutation.begin(), source_position)); + + auto input = Individuum{}; + auto output = Individuum{}; + if (config.mh_diversify_best) { + island.get_best_individuum(input); + } else { + island.get_random_individuum(input); + } + exchange_individum(config, graph, source, destination, input, output); + if (replace) { + island.replace(input, output); + } else { + island.insert(graph, output); + } +} - MPI_Status st; - MPI_Sendrecv( in.partition_map , G.number_of_nodes(), MPI_INT, to, 0, - out.partition_map, G.number_of_nodes(), MPI_INT, from, 0, m_communicator, &st); +void exchanger::quick_start(PartitionConfig& config, + graph_access& graph, + population& island) { + static_cast( + observe_graph_order(static_cast(graph.number_of_nodes()), + "evolutionary quick-start")); + auto const plan = ::kahip::parallel_mh::quick_start_population_plan( + config.mh_pool_size, m_size); + if (!plan.has_value()) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, "evolutionary quick-start", + "quick-start requires a positive communicator size"); + } + std::cout << "creating " << plan->local_creations << std::endl; + for (auto index = 0U; index < plan->local_creations; ++index) { + auto individual = Individuum{}; + island.createIndividuum(config, graph, individual, true); + island.insert(graph, individual); + } + + auto diversify_config = config; + diversify_config.mh_diversify_best = false; + for (auto index = 0U; index < plan->diversifications; ++index) { + diversify_population(diversify_config, graph, island, false); + } +} - //recompute cut edges and edge cut locally - forall_nodes(G, node) { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if(partition_map[node] != partition_map[target]) { - out.cut_edges->push_back(e); - } - } endfor - } endfor +void exchanger::exchange_individum(PartitionConfig const& config, + graph_access& graph, + int source, + int destination, + Individuum& input, + Individuum& output) { + auto const graph_count = + observe_graph_order(static_cast(graph.number_of_nodes()), + "MPI_Sendrecv(evolutionary permutation exchange)"); + if (source < 0 || source >= m_size || destination < 0 || + destination >= m_size || input.partition_map == nullptr) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, "MPI_Sendrecv(evolutionary permutation exchange)", + "permutation exchange arguments are invalid"); + } + + auto partition_map = std::make_unique( + static_cast(graph.number_of_nodes())); + auto cut_edges = std::make_unique>(); + auto status = MPI_Status{}; + ::kahip::parallel_mh::detail::check_mpi( + MPI_Sendrecv(input.partition_map, graph_count, MPI_INT, destination, 0, + partition_map.get(), graph_count, MPI_INT, source, 0, + m_communicator, &status), + m_communicator, "MPI_Sendrecv(evolutionary permutation exchange)"); + validate_partition_status(status, source, 0, graph_count, + "MPI_Sendrecv(evolutionary permutation exchange)"); + + forall_nodes(graph, node){forall_out_edges( + graph, edge, node){auto const target = graph.getEdgeTarget(edge); + if (partition_map[node] != partition_map[target]) { + cut_edges->push_back(edge); + } +} +endfor +} // node scope +endfor output.objective = m_qm.objective(config, graph, partition_map.get()); +output.partition_map = partition_map.release(); +output.cut_edges = cut_edges.release(); +} - out.objective = m_qm.objective(config, G, partition_map); +void exchanger::push_best(PartitionConfig& config, + graph_access& graph, + population& island) { + static_cast(config); + auto const graph_count = + observe_graph_order(static_cast(graph.number_of_nodes()), + "MPI_Isend(evolutionary rumor)"); + auto best = Individuum{}; + island.get_best_individuum(best); + if (::kahip::parallel_mh::objective_improved(best.objective, + m_prev_best_objective)) { + m_prev_best_objective = best.objective; + std::ranges::fill(m_already_sent_to, false); + m_already_sent_to[static_cast(m_rank)] = true; + m_cur_num_pushes = 0; + std::cout << "rank " << m_rank + << ": pool improved *************************************** " + << best.objective << std::endl; + } + + auto something_to_do = + std::ranges::any_of(m_already_sent_to, [](bool sent) { return !sent; }); + if (m_cur_num_pushes > m_max_num_pushes) + something_to_do = false; + if (something_to_do) { + auto payload = + std::vector(static_cast(graph.number_of_nodes())); + forall_nodes(graph, node) { + payload[static_cast(node)] = graph.getPartitionIndex(node); + } + endfor + + auto target = m_rank; + // Retain the paper's asynchronous rumor selection and exact draw order. + while (m_already_sent_to[static_cast(target)]) { + target = random_functions::nextInt(0, m_size - 1); + } + auto& issued = m_issued_sends[static_cast(target)]; + if (issued == std::numeric_limits::max()) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, "MPI_Isend(evolutionary rumor)", + "evolutionary rumor send count exceeds uint64_t"); + } + m_pending_sends.emplace_back(std::move(payload), MPI_REQUEST_NULL, + m_communicator); + auto& pending = m_pending_sends.back(); + ::kahip::parallel_mh::detail::check_mpi( + MPI_Isend(pending.payload.data(), graph_count, MPI_INT, target, target, + m_communicator, &pending.request), + m_communicator, "MPI_Isend(evolutionary rumor)"); + ++issued; + ++m_cur_num_pushes; + m_already_sent_to[static_cast(target)] = true; + } + retire_completed_sends(); } +void exchanger::retire_completed_sends() { + std::erase_if(m_pending_sends, [&](pending_send& pending) { + auto complete = 0; + ::kahip::parallel_mh::detail::check_mpi( + MPI_Test(&pending.request, &complete, MPI_STATUS_IGNORE), + m_communicator, "MPI_Test(evolutionary rumor)"); + return complete != 0; + }); +} -//extended push protocol -- see paper for details -void exchanger::push_best( PartitionConfig & config, graph_access & G, population & island ) { - int rank, size; - MPI_Comm_rank( m_communicator, &rank); - MPI_Comm_size( m_communicator, &size); - - Individuum best_ind; - island.get_best_individuum(best_ind); - - if( best_ind.objective < m_prev_best_objective) { - m_prev_best_objective = best_ind.objective; - for( unsigned i = 0; i < m_allready_send_to.size(); i++) { - m_allready_send_to[i] = false; - } - - m_allready_send_to[rank] = true; - m_cur_num_pushes = 0; - - std::cout << "rank " << rank - << ": pool improved *************************************** " - << best_ind.objective << std::endl; - } - - bool something_todo = false; - for( unsigned i = 0; i < m_allready_send_to.size(); i++) { - if(!m_allready_send_to[i]) { - something_todo = true; - break; - } - } - - if( m_cur_num_pushes > m_max_num_pushes ) { - something_todo = false; - } - - if(something_todo) { - int* partition_map = new int[G.number_of_nodes()]; - forall_nodes(G, node) { - partition_map[node] = G.getPartitionIndex(node); - } endfor - - int target = rank; - while( m_allready_send_to[target] ) { - //while (target == rank) { // m_allready_send_to[rank] always true - target = random_functions::nextInt(0, size-1); - //} - } - - MPI_Request* rq = new MPI_Request; - MPI_Isend( partition_map, G.number_of_nodes(), MPI_INT, target, target, m_communicator, rq); - - m_cur_num_pushes++; - - m_request_pointers.push_back( rq ); - m_partition_map_buffers.push_back( partition_map ); - - m_allready_send_to[target] = true; - } - - for( unsigned i = 0; i < m_request_pointers.size(); i++) { - int finished = 0; - MPI_Status st; - MPI_Test( m_request_pointers[i], &finished, &st); - - if(finished) { - std::swap(m_request_pointers[i], m_request_pointers[m_request_pointers.size()-1]); - std::swap(m_partition_map_buffers[i], m_partition_map_buffers[m_request_pointers.size()-1]); - - delete[] m_partition_map_buffers[m_partition_map_buffers.size() - 1]; - delete m_request_pointers[m_request_pointers.size() - 1]; - - m_partition_map_buffers.pop_back(); - m_request_pointers.pop_back(); - } - } +void exchanger::receive_available(PartitionConfig& config, + graph_access& graph, + population& island, + MPI_Status const& probe_status, + int graph_count) { + validate_partition_status(probe_status, probe_status.MPI_SOURCE, m_rank, + graph_count, "MPI_Recv(evolutionary rumor)"); + auto const source = probe_status.MPI_SOURCE; + auto partition_map = std::make_unique( + static_cast(graph.number_of_nodes())); + auto cut_edges = std::make_unique>(); + auto receive_status = MPI_Status{}; + ::kahip::parallel_mh::detail::check_mpi( + MPI_Recv(partition_map.get(), graph_count, MPI_INT, source, m_rank, + m_communicator, &receive_status), + m_communicator, "MPI_Recv(evolutionary rumor)"); + validate_partition_status(receive_status, source, m_rank, graph_count, + "MPI_Recv(evolutionary rumor)"); + + forall_nodes(graph, node){forall_out_edges( + graph, edge, node){auto const target = graph.getEdgeTarget(edge); + if (partition_map[node] != partition_map[target]) { + cut_edges->push_back(edge); + } +} +endfor +} +endfor auto output = Individuum{}; +output.objective = m_qm.objective(config, graph, partition_map.get()); +output.partition_map = partition_map.release(); +output.cut_edges = cut_edges.release(); +island.insert(graph, output); + +auto& consumed = m_consumed_receives[static_cast(source)]; +if (consumed == std::numeric_limits::max()) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, "MPI_Recv(evolutionary rumor)", + "evolutionary rumor receive count exceeds uint64_t"); +} +++consumed; +if (::kahip::parallel_mh::objective_improved(output.objective, + m_prev_best_objective)) { + m_prev_best_objective = output.objective; + std::cout << "rank " << m_rank + << ": pool improved (inc) " + "**************************************** " + << output.objective << std::endl; + std::ranges::fill(m_already_sent_to, false); + m_already_sent_to[static_cast(m_rank)] = true; + m_cur_num_pushes = 0; +} +m_already_sent_to[static_cast(source)] = true; } -void exchanger::recv_incoming( PartitionConfig & config, graph_access & G, population & island ) { - int rank; - MPI_Comm_rank( m_communicator, &rank); - - int flag; MPI_Status st; - MPI_Iprobe(MPI_ANY_SOURCE, MPI_ANY_TAG, m_communicator, &flag, &st); - - while(flag) { - Individuum out; - int* partition_map = new int[G.number_of_nodes()]; - out.partition_map = partition_map; - out.cut_edges = new std::vector(); - - MPI_Status rst; - MPI_Recv( out.partition_map, G.number_of_nodes(), MPI_INT, st.MPI_SOURCE, rank, m_communicator, &rst); - - //recompute cut edges and edge cut locally - forall_nodes(G, node) { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if(partition_map[node] != partition_map[target]) { - out.cut_edges->push_back(e); - } - } endfor - } endfor - - out.objective = m_qm.objective(config, G, partition_map); - island.insert( G, out ); - - if( (unsigned)out.objective < (unsigned)m_prev_best_objective) { - m_prev_best_objective = out.objective; - std::cout << "rank " << rank - << ": pool improved (inc) **************************************** " - << out.objective << std::endl; - - for( unsigned i = 0; i < m_allready_send_to.size(); i++) { - m_allready_send_to[i] = false; - } - - m_allready_send_to[rank] = true; - m_cur_num_pushes = 0; - } - - m_allready_send_to[st.MPI_SOURCE] = true; // we dont need to send it back - saves us P * 1 messages of length n - - MPI_Iprobe(MPI_ANY_SOURCE, MPI_ANY_TAG, m_communicator, &flag, &st); - } +void exchanger::recv_incoming(PartitionConfig& config, + graph_access& graph, + population& island) { + auto const graph_count = + observe_graph_order(static_cast(graph.number_of_nodes()), + "MPI_Recv(evolutionary rumor)"); + auto available = 0; + auto probe_status = MPI_Status{}; + ::kahip::parallel_mh::detail::check_mpi( + MPI_Iprobe(MPI_ANY_SOURCE, MPI_ANY_TAG, m_communicator, &available, + &probe_status), + m_communicator, "MPI_Iprobe(evolutionary rumor)"); + while (available != 0) { + receive_available(config, graph, island, probe_status, graph_count); + ::kahip::parallel_mh::detail::check_mpi( + MPI_Iprobe(MPI_ANY_SOURCE, MPI_ANY_TAG, m_communicator, &available, + &probe_status), + m_communicator, "MPI_Iprobe(evolutionary rumor)"); + } } +void exchanger::finish(std::size_t graph_order) { + auto const graph_count = + observe_graph_order(graph_order, "evolutionary rumor exchange finish"); + auto incoming = std::vector(static_cast(m_size)); + ::kahip::parallel_mh::detail::check_mpi( + MPI_Alltoall(m_issued_sends.data(), 1, MPI_UINT64_T, incoming.data(), 1, + MPI_UINT64_T, m_communicator), + m_communicator, "MPI_Alltoall(evolutionary rumor counts)"); + + for (auto source = 0; source < m_size; ++source) { + auto& consumed = m_consumed_receives[static_cast(source)]; + auto const expected = incoming[static_cast(source)]; + if (consumed > expected) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, "evolutionary rumor exchange finish", + "consumed rumor count exceeds the sender's issued count"); + } + while (consumed < expected) { + auto probe_status = MPI_Status{}; + ::kahip::parallel_mh::detail::check_mpi( + MPI_Probe(source, MPI_ANY_TAG, m_communicator, &probe_status), + m_communicator, "MPI_Probe(evolutionary rumor drain)"); + validate_partition_status(probe_status, source, m_rank, graph_count, + "MPI_Recv(evolutionary rumor drain)"); + auto payload = std::vector(graph_order); + auto receive_status = MPI_Status{}; + ::kahip::parallel_mh::detail::check_mpi( + MPI_Recv(payload.data(), graph_count, MPI_INT, source, m_rank, + m_communicator, &receive_status), + m_communicator, "MPI_Recv(evolutionary rumor drain)"); + validate_partition_status(receive_status, source, m_rank, graph_count, + "MPI_Recv(evolutionary rumor drain)"); + ++consumed; + } + } + + for (auto& pending : m_pending_sends) { + ::kahip::parallel_mh::detail::check_mpi( + MPI_Wait(&pending.request, MPI_STATUS_IGNORE), m_communicator, + "MPI_Wait(evolutionary rumor)"); + } + m_pending_sends.clear(); + m_finished = true; +} diff --git a/lib/parallel_mh/exchange/exchanger.h b/lib/parallel_mh/exchange/exchanger.h index b7e1f349..9297ccbf 100644 --- a/lib/parallel_mh/exchange/exchanger.h +++ b/lib/parallel_mh/exchange/exchanger.h @@ -1,5 +1,5 @@ /****************************************************************************** - * exchanger.h + * exchanger.h * * * Source of KaHIP -- Karlsruhe High Quality Partitioning. * Christian Schulz @@ -10,42 +10,99 @@ #include +#include +#include +#include +#include +#include + #include "data_structure/graph_access.h" #include "parallel_mh/population.h" #include "partition_config.h" #include "tools/quality_metrics.h" +class exchanger final { + public: + explicit exchanger(MPI_Comm communicator); + ~exchanger() noexcept; -class exchanger { -public: - exchanger( MPI_Comm communicator ); - virtual ~exchanger(); + exchanger(exchanger const&) = delete; + auto operator=(exchanger const&) -> exchanger& = delete; + exchanger(exchanger&&) = delete; + auto operator=(exchanger&&) -> exchanger& = delete; - void diversify_population( PartitionConfig & config, graph_access & G, population & island, bool replace ); - void quick_start( PartitionConfig & config, graph_access & G, population & island ); - void push_best( PartitionConfig & config, graph_access & G, population & island ); - void recv_incoming( PartitionConfig & config, graph_access & G, population & island ); + void diversify_population(PartitionConfig& config, + graph_access& graph, + population& island, + bool replace); + void quick_start(PartitionConfig& config, + graph_access& graph, + population& island); + void push_best(PartitionConfig& config, + graph_access& graph, + population& island); + void recv_incoming(PartitionConfig& config, + graph_access& graph, + population& island); + void finish(std::size_t graph_order); -private: - void exchange_individum(const PartitionConfig & config, - graph_access & G, - int & from, - int & rank, - int & to, - Individuum & in, Individuum & out); + private: + struct pending_send final { + std::vector payload; + MPI_Request request = MPI_REQUEST_NULL; + MPI_Comm communicator = MPI_COMM_NULL; - std::vector< int* > m_partition_map_buffers; - std::vector< MPI_Request* > m_request_pointers; - std::vector m_allready_send_to; + pending_send(std::vector values, + MPI_Request handle, + MPI_Comm failure_communicator) noexcept + : payload(std::move(values)), + request(handle), + communicator(failure_communicator) {} + pending_send(pending_send const&) = delete; + auto operator=(pending_send const&) -> pending_send& = delete; + pending_send(pending_send&& other) noexcept + : payload(std::move(other.payload)), + request(std::exchange(other.request, MPI_REQUEST_NULL)), + communicator(std::exchange(other.communicator, MPI_COMM_NULL)) {} + auto operator=(pending_send&& other) noexcept -> pending_send&; + }; - int m_prev_best_objective; - int m_max_num_pushes; - int m_cur_num_pushes; + void exchange_individum(PartitionConfig const& config, + graph_access& graph, + int source, + int destination, + Individuum& input, + Individuum& output); + [[nodiscard]] auto observe_graph_order(std::size_t graph_order, + std::string_view operation) -> int; + void validate_partition_status(MPI_Status const& status, + int expected_source, + int expected_tag, + int expected_count, + std::string_view operation) const; + void receive_available(PartitionConfig& config, + graph_access& graph, + population& island, + MPI_Status const& probe_status, + int graph_count); + void retire_completed_sends(); - MPI_Comm m_communicator; + std::vector m_pending_sends; + std::vector m_already_sent_to; + std::vector m_issued_sends; + std::vector m_consumed_receives; - quality_metrics m_qm; -}; + EdgeWeight m_prev_best_objective; + int m_max_num_pushes; + int m_cur_num_pushes = 0; + int m_rank; + int m_size; + std::size_t m_graph_order = 0; + bool m_graph_order_observed = false; + bool m_finished = false; + MPI_Comm m_communicator; + quality_metrics m_qm; +}; #endif /* end of include guard: EXCHANGER_YPB6QKNL */ diff --git a/lib/parallel_mh/parallel_mh_async.cpp b/lib/parallel_mh/parallel_mh_async.cpp index 7e717741..3e2cd2f8 100644 --- a/lib/parallel_mh/parallel_mh_async.cpp +++ b/lib/parallel_mh/parallel_mh_async.cpp @@ -1,16 +1,19 @@ /****************************************************************************** - * parallel_mh_async.cpp + * parallel_mh_async.cpp * * * Source of KaHIP -- Karlsruhe High Quality Partitioning. * Christian Schulz *****************************************************************************/ #include -#include +#include +#include +#include #include +#include #include #include -#include +#include #include "diversifyer.h" #include "exchange/exchanger.h" @@ -18,305 +21,278 @@ #include "graph_io.h" #include "graph_partitioner.h" #include "parallel_mh_async.h" +#include "parallel_mh/evolutionary_collectives.h" +#include "parallel_mh/evolutionary_feasibility.h" +#include "parallel_mh/population_size_broadcast.h" +#include "../../parallel/shared/random_state.h" #include "quality_metrics.h" #include "random_functions.h" +parallel_mh_async::parallel_mh_async() + : parallel_mh_async(MPI_COMM_WORLD) {} -parallel_mh_async::parallel_mh_async() : MASTER(0), m_time_limit(0) { - m_best_global_objective = std::numeric_limits::max(); - m_best_cycle_objective = std::numeric_limits::max(); - m_rounds = 0; - m_termination = false; - m_communicator = MPI_COMM_WORLD; - MPI_Comm_rank( m_communicator, &m_rank); - MPI_Comm_size( m_communicator, &m_size); -} - -parallel_mh_async::parallel_mh_async(MPI_Comm communicator) : MASTER(0), m_time_limit(0) { - m_best_global_objective = std::numeric_limits::max(); - m_best_cycle_objective = std::numeric_limits::max(); - m_rounds = 0; - m_termination = false; - m_communicator = communicator; - MPI_Comm_rank( m_communicator, &m_rank); - MPI_Comm_size( m_communicator, &m_size); - -} +parallel_mh_async::parallel_mh_async(MPI_Comm communicator) + : m_communicator( + std::make_unique< + ::kahip::parallel_mh::owned_evolutionary_communicator>( + communicator)), + m_rank(m_communicator->rank()), + m_size(m_communicator->size()) {} -parallel_mh_async::~parallel_mh_async() { - delete[] m_best_global_map; -} +parallel_mh_async::~parallel_mh_async() = default; void parallel_mh_async::perform_partitioning(const PartitionConfig & partition_config, graph_access & G) { - m_time_limit = partition_config.time_limit; - m_island = new population(m_communicator, partition_config); - m_best_global_map = new PartitionID[G.number_of_nodes()]; - - srand(partition_config.seed*m_size+m_rank); - random_functions::setSeed(partition_config.seed*m_size+m_rank); - - PartitionConfig ini_working_config = partition_config; - initialize( ini_working_config, G); - - m_t.restart(); - exchanger ex(m_communicator); - do { - PartitionConfig working_config = partition_config; - - working_config.graph_allready_partitioned = false; - if(!partition_config.strong) - working_config.no_new_initial_partitioning = false; - - working_config.mh_pool_size = ini_working_config.mh_pool_size; - if(m_rounds == 0 && working_config.mh_enable_quickstart) { - ex.quick_start( working_config, G, *m_island ); - } - - perform_local_partitioning( working_config, G ); - if(m_rank == ROOT) { - std::cout << "t left " << (m_time_limit - m_t.elapsed()) << std::endl; - } - - //push and recv - if( m_t.elapsed() <= m_time_limit && m_size > 1) { - unsigned messages = ceil(log(m_size)); - for( unsigned i = 0; i < messages; i++) { - ex.push_best( working_config, G, *m_island ); - ex.recv_incoming( working_config, G, *m_island ); - } - } - - m_rounds++; - } while( m_t.elapsed() <= m_time_limit ); - - collect_best_partitioning(G, partition_config); - m_island->print(); - - //print logfile (for convergence plots) - if( partition_config.mh_print_log ) { - std::stringstream filename_stream; - filename_stream << "log_"<< partition_config.graph_filename << - "_m_rank_" << m_rank << - "_file_" << - "_seed_" << partition_config.seed << - "_k_" << partition_config.k; - - std::string filename(filename_stream.str()); - m_island->write_log(filename); + m_time_limit = partition_config.time_limit; + m_rounds = 0; + m_island = std::make_unique(m_communicator->native_handle(), + partition_config); + + auto const local_seed = ::kahip::random_compat::mixed_rank_seed( + partition_config.seed, m_size, m_rank); + if (!local_seed.has_value()) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator->native_handle(), "evolutionary random seed derivation", + "process count and rank must identify a valid communicator member"); + } + std::srand(*local_seed); + random_functions::setSeed(*local_seed); + + PartitionConfig ini_working_config = partition_config; + initialize( ini_working_config, G); + + m_t.restart(); + { + // Isend/Iprobe intentionally implement the paper's asynchronous + // evolutionary rumor spreading, not a collective-shaped redistribution. + // exchanger::finish still gives every request and payload an exact, + // collective teardown lifetime before this scope ends. + exchanger ex(m_communicator->native_handle()); + do { + PartitionConfig working_config = partition_config; + + working_config.graph_allready_partitioned = false; + if(!partition_config.strong) + working_config.no_new_initial_partitioning = false; + + working_config.mh_pool_size = ini_working_config.mh_pool_size; + if(m_rounds == 0 && working_config.mh_enable_quickstart) { + ex.quick_start( working_config, G, *m_island ); + } + + perform_local_partitioning( working_config, G ); + if(m_rank == ROOT) { + std::cout << "t left " << (m_time_limit - m_t.elapsed()) << std::endl; + } + + //push and recv + if( m_t.elapsed() <= m_time_limit && m_size > 1) { + auto const messages = + static_cast(std::ceil(std::log(m_size))); + for( unsigned i = 0; i < messages; i++) { + ex.push_best( working_config, G, *m_island ); + ex.recv_incoming( working_config, G, *m_island ); } - - delete m_island; + } + + m_rounds++; + } while( m_t.elapsed() <= m_time_limit ); + ex.finish(static_cast(G.number_of_nodes())); + } + + EdgeWeight min_objective = 0; + m_island->apply_fittest(G, min_objective); + collect_best_partitioning(G, partition_config, min_objective); + m_island->print(); + + //print logfile (for convergence plots) + if( partition_config.mh_print_log ) { + std::stringstream filename_stream; + filename_stream << "log_"<< partition_config.graph_filename << + "_m_rank_" << m_rank << + "_file_" << + "_seed_" << partition_config.seed << + "_k_" << partition_config.k; + + std::string filename(filename_stream.str()); + m_island->write_log(filename); + } + + m_island.reset(); } void parallel_mh_async::initialize(PartitionConfig & working_config, graph_access & G) { - // each PE performs a partitioning - // estimate the runtime of a partitioner call - // calculate the poolsize and async Bcast the poolsize. - // recv. has to be sync - Individuum first_one; - m_t.restart(); - if( !working_config.mh_easy_construction) { - m_island->createIndividuum( working_config, G, first_one, true); - } else { - construct_partition cp; - cp.createIndividuum( working_config, G, first_one, true); - std::cout << "created with objective " << first_one.objective << std::endl; - } - - double time_spend = m_t.elapsed(); - m_island->insert(G, first_one); - - //compute S and Bcast - int population_size = 1; - double fraction = working_config.mh_initial_population_fraction; - int POPSIZE_TAG = 10; - - if( m_rank == ROOT ) { - double fraction_to_spend_for_IP = (double)m_time_limit / fraction; - population_size = ceil(fraction_to_spend_for_IP / time_spend); - - for( int target = 1; target < m_size; target++) { - MPI_Request rq; - MPI_Isend(&population_size, 1, MPI_INT, target, POPSIZE_TAG, m_communicator, &rq); - } - } else { - MPI_Status rst; - MPI_Recv(&population_size, 1, MPI_INT, ROOT, POPSIZE_TAG, m_communicator, &rst); - } - - MPI_Barrier(MPI_COMM_WORLD); - - population_size = std::max(3, population_size); - if(working_config.mh_easy_construction) { - population_size = std::min(50, population_size); - } else { - population_size = std::min(100, population_size); - } - std::cout << "poolsize = " << population_size << std::endl; - - //set S - m_island->set_pool_size(population_size); - working_config.mh_pool_size = population_size; + // each PE performs a partitioning + // estimate the runtime of a partitioner call + // calculate the poolsize and broadcast it to the communicator. + Individuum first_one; + m_t.restart(); + if( !working_config.mh_easy_construction) { + m_island->createIndividuum( working_config, G, first_one, true); + } else { + construct_partition cp; + cp.createIndividuum( working_config, G, first_one, true); + std::cout << "created with objective " << first_one.objective << std::endl; + } + + double time_spend = m_t.elapsed(); + m_island->insert(G, first_one); + + //compute S and Bcast + int population_size = 1; + double fraction = working_config.mh_initial_population_fraction; + + if( m_rank == ROOT ) { + auto const estimate = ::kahip::parallel_mh::estimate_population_size( + m_time_limit, fraction, time_spend, + working_config.mh_easy_construction); + if (!estimate.has_value()) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator->native_handle(), + "evolutionary population-size estimation", + "time limit, initial fraction, and elapsed time must be finite and " + "within their valid domains"); + } + population_size = *estimate; + } + + population_size = ::kahip::parallel_mh::broadcast_population_size( + m_communicator->native_handle(), population_size, + working_config.mh_easy_construction); + std::cout << "poolsize = " << population_size << std::endl; + + //set S + m_island->set_pool_size(population_size); + working_config.mh_pool_size = population_size; } -EdgeWeight parallel_mh_async::collect_best_partitioning(graph_access & G, const PartitionConfig & config) { - //perform partitioning locally - EdgeWeight min_objective = 0; - m_island->apply_fittest(G, min_objective); - - int best_local_objective = min_objective; - int best_local_objective_m = min_objective; - int best_global_objective = 0; - - PartitionID* best_local_map = new PartitionID[G.number_of_nodes()]; - std::vector< NodeWeight > block_sizes(G.get_partition_count(),0); - - forall_nodes(G, node) { - best_local_map[node] = G.getPartitionIndex(node); - block_sizes[G.getPartitionIndex(node)]++; - } endfor - - NodeWeight max_domain_weight = 0; - for( unsigned i = 0; i < G.get_partition_count(); i++) { - if( block_sizes[i] > max_domain_weight ) { - max_domain_weight = block_sizes[i]; - } - } - - if( max_domain_weight > config.upper_bound_partition ) { - best_local_objective_m = std::numeric_limits< int >::max(); - } - - MPI_Allreduce(&best_local_objective_m, &best_global_objective, 1, MPI_INT, MPI_MIN, m_communicator); - - if( best_global_objective == std::numeric_limits< int >::max()) { - //no partition is feasible - MPI_Allreduce(&best_local_objective, &best_global_objective, 1, MPI_INT, MPI_MIN, m_communicator); - } - - int my_domain_weight = best_local_objective == best_global_objective ? - max_domain_weight : std::numeric_limits::max(); - int best_domain_weight = max_domain_weight; - - MPI_Allreduce(&my_domain_weight, &best_domain_weight, 1, MPI_INT, MPI_MIN, m_communicator); - - // now we know what the best objective is ... find the best balance - int bcaster = best_local_objective == best_global_objective - && my_domain_weight == best_domain_weight ? m_rank : std::numeric_limits::max(); - int g_bcaster = 0; - - MPI_Allreduce(&bcaster, &g_bcaster, 1, MPI_INT, MPI_MIN, m_communicator); - MPI_Bcast(best_local_map, G.number_of_nodes(), MPI_INT, g_bcaster, m_communicator); - - forall_nodes(G, node) { - G.setPartitionIndex(node, best_local_map[node]); - } endfor - - delete[] best_local_map; - - return best_global_objective; +EdgeWeight parallel_mh_async::collect_best_partitioning( + graph_access& G, + PartitionConfig const& config, + EdgeWeight min_objective) { + std::vector best_local_map(G.number_of_nodes()); + forall_nodes(G, node) { + best_local_map[node] = G.getPartitionIndex(node); + } endfor + + auto const max_domain_weight = + ::kahip::parallel_mh::maximum_block_weight(G); + if (!max_domain_weight.has_value()) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator->native_handle(), "evolutionary feasibility accounting", + "partition labels or block-weight sums exceed their valid domains"); + } + + auto const best_global_objective = + ::kahip::parallel_mh::select_and_broadcast_best_partition( + m_communicator->native_handle(), min_objective, *max_domain_weight, + config.upper_bound_partition, best_local_map.data(), + best_local_map.size()); + + forall_nodes(G, node) { + G.setPartitionIndex(node, best_local_map[node]); + } endfor + + return best_global_objective; } EdgeWeight parallel_mh_async::perform_local_partitioning(PartitionConfig & working_config, graph_access & G) { - quality_metrics qm; - unsigned local_repetitions = working_config.local_partitioning_repetitions; + quality_metrics qm; + unsigned local_repetitions = working_config.local_partitioning_repetitions; + + if( working_config.mh_diversify ) { + diversifyer div; + div.diversify(working_config); + } + + //start a new round + for( unsigned i = 0; i < local_repetitions; i++) { + if( working_config.mh_no_mh ) { + Individuum first_ind; + + if( !working_config.mh_easy_construction) { + m_island->createIndividuum(working_config, G, first_ind, true); + m_island->insert(G, first_ind); + } else { + construct_partition cp; + cp.createIndividuum( working_config, G, first_ind, true); + + m_island->insert(G, first_ind); + std::cout << "created with objective " << first_ind.objective << std::endl; + } + } else { + if( m_island->is_full() && !working_config.mh_disable_combine) { + + int decision = random_functions::nextInt(0,9); + Individuum output; + + if(decision < working_config.mh_flip_coin) { + m_island->mutate_random(working_config, G, output); + m_island->insert(G, output); + } else { - if( working_config.mh_diversify ) { - diversifyer div; - div.diversify(working_config); + int combine_decision = random_functions::nextInt(0,5); + if(combine_decision <= 4) { + Individuum first_rnd; + Individuum second_rnd; + if(working_config.mh_enable_tournament_selection) { + m_island->get_two_individuals_tournament(first_rnd, second_rnd); + } else { + m_island->get_two_random_individuals(first_rnd, second_rnd); + } + + m_island->combine(working_config, G, first_rnd, second_rnd, output); + + int coin = 0; + + if( working_config.mh_enable_gal_combine ) { + coin = random_functions::nextInt(0,100); + } + if( coin == 23 ) { + if( first_rnd.objective > second_rnd.objective) { + m_island->replace(first_rnd, output); + } else { + m_island->replace(second_rnd, output); + } + } else { + m_island->insert(G, output); + } + } else if( combine_decision == 5 ) { + if(!working_config.mh_disable_cross_combine) { + Individuum selected; + m_island->get_one_individual_tournament(selected); + m_island->combine_cross(working_config, G, selected, output); + m_island->insert(G, output); + } + } } - //start a new round - for( unsigned i = 0; i < local_repetitions; i++) { - if( working_config.mh_no_mh ) { - Individuum first_ind; - - if( !working_config.mh_easy_construction) { - m_island->createIndividuum(working_config, G, first_ind, true); - m_island->insert(G, first_ind); - } else { - construct_partition cp; - cp.createIndividuum( working_config, G, first_ind, true); - - m_island->insert(G, first_ind); - std::cout << "created with objective " << first_ind.objective << std::endl; - } - } else { - if( m_island->is_full() && !working_config.mh_disable_combine) { - - int decision = random_functions::nextInt(0,9); - Individuum output; - - if(decision < working_config.mh_flip_coin) { - m_island->mutate_random(working_config, G, output); - m_island->insert(G, output); - } else { - - int combine_decision = random_functions::nextInt(0,5); - if(combine_decision <= 4) { - Individuum first_rnd; - Individuum second_rnd; - if(working_config.mh_enable_tournament_selection) { - m_island->get_two_individuals_tournament(first_rnd, second_rnd); - } else { - m_island->get_two_random_individuals(first_rnd, second_rnd); - } - - m_island->combine(working_config, G, first_rnd, second_rnd, output); - - int coin = 0; - - if( working_config.mh_enable_gal_combine ) { - coin = random_functions::nextInt(0,100); - } - if( coin == 23 ) { - if( first_rnd.objective > second_rnd.objective) { - m_island->replace(first_rnd, output); - } else { - m_island->replace(second_rnd, output); - } - } else { - m_island->insert(G, output); - } - } else if( combine_decision == 5 ) { - if(!working_config.mh_disable_cross_combine) { - Individuum selected; - m_island->get_one_individual_tournament(selected); - m_island->combine_cross(working_config, G, selected, output); - m_island->insert(G, output); - } - } - } - - } else { - Individuum first_ind; - if(m_island->is_full()) { - m_island->mutate_random(working_config, G, first_ind); - } else { - if( !working_config.mh_easy_construction) { - m_island->createIndividuum(working_config, G, first_ind, true); - } else { - construct_partition cp; - cp.createIndividuum( working_config, G, first_ind, true); - std::cout << "created with objective " << first_ind.objective << std::endl; - } - } - m_island->insert(G, first_ind); - } - } - - //try to combine to random inidividuals from pool - if( m_t.elapsed() > m_time_limit ) { - break; - } - + } else { + Individuum first_ind; + if(m_island->is_full()) { + m_island->mutate_random(working_config, G, first_ind); + } else { + if( !working_config.mh_easy_construction) { + m_island->createIndividuum(working_config, G, first_ind, true); + } else { + construct_partition cp; + cp.createIndividuum( working_config, G, first_ind, true); + std::cout << "created with objective " << first_ind.objective << std::endl; + } } + m_island->insert(G, first_ind); + } + } - EdgeWeight min_objective = 0; - m_island->apply_fittest(G, min_objective); + //try to combine to random inidividuals from pool + if( m_t.elapsed() > m_time_limit ) { + break; + } - return min_objective; -} + } + EdgeWeight min_objective = 0; + m_island->apply_fittest(G, min_objective); + return min_objective; +} diff --git a/lib/parallel_mh/parallel_mh_async.h b/lib/parallel_mh/parallel_mh_async.h index 34ba305c..a34779e1 100644 --- a/lib/parallel_mh/parallel_mh_async.h +++ b/lib/parallel_mh/parallel_mh_async.h @@ -1,5 +1,5 @@ /****************************************************************************** - * parallel_mh_async.h + * parallel_mh_async.h * * * Source of KaHIP -- Karlsruhe High Quality Partitioning. * Christian Schulz @@ -9,42 +9,48 @@ #define PARALLEL_MH_ASYNC_HF106Y0G #include + +#include + #include "data_structure/graph_access.h" #include "partition_config.h" #include "population.h" #include "timer.h" - -class parallel_mh_async { -public: - parallel_mh_async(); - parallel_mh_async(MPI_Comm communicator); - virtual ~parallel_mh_async(); - - void perform_partitioning(const PartitionConfig & graph_partitioner_config, graph_access & G); - void initialize(PartitionConfig & graph_partitioner_config, graph_access & G); - EdgeWeight perform_local_partitioning(PartitionConfig & graph_partitioner_config, graph_access & G); - EdgeWeight collect_best_partitioning(graph_access & G, const PartitionConfig & config); - void perform_cycle_partitioning(PartitionConfig & graph_partitioner_config, graph_access & G); - -private: - //misc - const unsigned MASTER; - timer m_t; - int m_rank; - int m_size; - double m_time_limit; - bool m_termination; - unsigned m_rounds; - - //the best cut found so far - PartitionID* m_best_global_map; - int m_best_global_objective; - int m_best_cycle_objective; - - //island - population* m_island; - MPI_Comm m_communicator; +namespace kahip::parallel_mh { +class owned_evolutionary_communicator; +} +class parallel_mh_async final { + public: + parallel_mh_async(); + explicit parallel_mh_async(MPI_Comm communicator); + ~parallel_mh_async(); + + parallel_mh_async(parallel_mh_async const&) = delete; + auto operator=(parallel_mh_async const&) -> parallel_mh_async& = delete; + parallel_mh_async(parallel_mh_async&&) = delete; + auto operator=(parallel_mh_async&&) -> parallel_mh_async& = delete; + + void perform_partitioning(PartitionConfig const& graph_partitioner_config, + graph_access& G); + void initialize(PartitionConfig& graph_partitioner_config, graph_access& G); + EdgeWeight perform_local_partitioning( + PartitionConfig& graph_partitioner_config, + graph_access& G); + EdgeWeight collect_best_partitioning(graph_access& G, + PartitionConfig const& config, + EdgeWeight min_objective); + void perform_cycle_partitioning(PartitionConfig& graph_partitioner_config, + graph_access& G); + + private: + std::unique_ptr<::kahip::parallel_mh::owned_evolutionary_communicator> + m_communicator; + timer m_t; + int m_rank; + int m_size; + double m_time_limit = 0.0; + unsigned m_rounds = 0; + std::unique_ptr m_island; }; - #endif /* end of include guard: PARALLEL_MH_ASYNC_HF106Y0G */ diff --git a/lib/parallel_mh/population.cpp b/lib/parallel_mh/population.cpp index ca0526b9..c20b8d95 100644 --- a/lib/parallel_mh/population.cpp +++ b/lib/parallel_mh/population.cpp @@ -1,5 +1,5 @@ /****************************************************************************** - * population.cpp + * population.cpp * * * Source of KaHIP -- Karlsruhe High Quality Partitioning. * Christian Schulz @@ -11,436 +11,454 @@ #include #include #include +#include #include "diversifyer.h" #include "galinier_combine/gal_combine.h" #include "graph_partitioner.h" +#include "parallel_mh/evolutionary_collectives.h" #include "population.h" #include "quality_metrics.h" #include "random_functions.h" #include "timer.h" #include "uncoarsening/refinement/cycle_improvements/cycle_refinement.h" - -population::population( MPI_Comm communicator, const PartitionConfig & partition_config ) { - m_population_size = partition_config.mh_pool_size; - m_no_partition_calls = 0; - m_num_NCs = partition_config.mh_num_ncs_to_compute; - m_num_NCs_computed = 0; - m_num_ENCs = 0; - m_time_stamp = 0; - m_communicator = communicator; - m_global_timer.restart(); +namespace { +class null_streambuf final : public std::streambuf { + protected: + auto overflow(traits_type::int_type character) + -> traits_type::int_type override { + return traits_type::not_eof(character); + } +}; + +class scoped_output_suppression final { + public: + scoped_output_suppression() : previous_(std::cout.rdbuf(&sink_)) {} + ~scoped_output_suppression() { std::cout.rdbuf(previous_); } + + scoped_output_suppression(scoped_output_suppression const&) = delete; + auto operator=(scoped_output_suppression const&) + -> scoped_output_suppression& = delete; + + private: + null_streambuf sink_; + std::streambuf* previous_; +}; +} // namespace + +population::population(MPI_Comm communicator, + PartitionConfig const& partition_config) + : m_population_size(partition_config.mh_pool_size), + m_num_NCs(partition_config.mh_num_ncs_to_compute), + m_communicator(communicator) { + m_global_timer.restart(); } population::~population() { - for( unsigned i = 0; i < m_internal_population.size(); i++) { - delete[] (m_internal_population[i].partition_map); - delete m_internal_population[i].cut_edges; - } + for( unsigned i = 0; i < m_internal_population.size(); i++) { + delete[] (m_internal_population[i].partition_map); + delete m_internal_population[i].cut_edges; + } } void population::set_pool_size(int size) { - m_population_size = size; + m_population_size = size; } -void population::createIndividuum(const PartitionConfig & config, - graph_access & G, - Individuum & ind, bool output) { - - PartitionConfig copy = config; - graph_partitioner partitioner; - quality_metrics qm; - - std::ofstream ofs; - std::streambuf* backup = std::cout.rdbuf(); -#ifdef _WIN32 - ofs.open("NUL"); -#else - ofs.open("/dev/null"); -#endif - std::cout.rdbuf(ofs.rdbuf()); - - timer t; t.restart(); - - if(config.buffoon) { // graph is weighted -> no negative cycle detection yet - partitioner.perform_partitioning(copy, G); - ofs.close(); - std::cout.rdbuf(backup); - } else { - if(config.kabapE) { - double real_epsilon = config.imbalance/100.0; - double lb = real_epsilon+0.005; - double ub = real_epsilon+config.kabaE_internal_bal; - double epsilon = random_functions::nextDouble(lb,ub); - copy.upper_bound_partition = (1+epsilon)*ceil(config.largest_graph_weight/(double)config.k); - - partitioner.perform_partitioning(copy, G); - - ofs.close(); - std::cout.rdbuf(backup); - - complete_boundary boundary(&G); - boundary.build(); - - copy = config; - - diversifyer df; - df.diversify_kaba(copy); - - cycle_refinement cr; - cr.perform_refinement(copy, G, boundary); - } else { - partitioner.perform_partitioning(copy, G); - ofs.close(); - std::cout.rdbuf(backup); - } - } - - int* partition_map = new int[G.number_of_nodes()]; - - forall_nodes(G, node) { - partition_map[node] = G.getPartitionIndex(node); - } endfor +void population::createIndividuum(const PartitionConfig & config, + graph_access & G, + Individuum & ind, bool output) { + + PartitionConfig copy = config; + graph_partitioner partitioner; + quality_metrics qm; + + if(config.buffoon) { // graph is weighted -> no negative cycle detection yet + auto suppression = scoped_output_suppression{}; + partitioner.perform_partitioning(copy, G); + } else { + if(config.kabapE) { + double real_epsilon = config.imbalance/100.0; + double lb = real_epsilon+0.005; + double ub = real_epsilon+config.kabaE_internal_bal; + double epsilon = random_functions::nextDouble(lb,ub); + copy.upper_bound_partition = (1+epsilon)*ceil(config.largest_graph_weight/(double)config.k); + + { + auto suppression = scoped_output_suppression{}; + partitioner.perform_partitioning(copy, G); + } + + complete_boundary boundary(&G); + boundary.build(); + + copy = config; + + diversifyer df; + df.diversify_kaba(copy); + + cycle_refinement cr; + cr.perform_refinement(copy, G, boundary); + } else { + auto suppression = scoped_output_suppression{}; + partitioner.perform_partitioning(copy, G); + } + } + + int* partition_map = new int[G.number_of_nodes()]; + + forall_nodes(G, node) { + partition_map[node] = G.getPartitionIndex(node); + } endfor + + ind.objective = qm.objective(config, G, partition_map); + ind.partition_map = partition_map; + ind.cut_edges = new std::vector(); + + forall_nodes(G, node) { + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + if(partition_map[node] != partition_map[target]) { + ind.cut_edges->push_back(e); + } + } endfor +} endfor + +if(output) { + m_filebuffer_string << m_global_timer.elapsed() << " " << ind.cut_edges->size()/2 << std::endl; + m_time_stamp++; +} +} - ind.objective = qm.objective(config, G, partition_map); - ind.partition_map = partition_map; - ind.cut_edges = new std::vector(); +void population::insert(graph_access & G, Individuum & ind) { - forall_nodes(G, node) { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if(partition_map[node] != partition_map[target]) { - ind.cut_edges->push_back(e); - } - } endfor - } endfor + m_no_partition_calls++; + if(m_internal_population.size() < m_population_size) { + m_internal_population.push_back(ind); + } else { + EdgeWeight worst_objective = 0; + for( unsigned i = 0; i < m_internal_population.size(); i++) { + if(m_internal_population[i].objective > worst_objective) { + worst_objective = m_internal_population[i].objective; + } + } + if(ind.objective > worst_objective ) { + delete[] (ind.partition_map); + delete ind.cut_edges; + return; // do nothing + } + //else measure similarity + unsigned max_similarity = std::numeric_limits::max(); + unsigned max_similarity_idx = 0; + for( unsigned i = 0; i < m_internal_population.size(); i++) { + if(m_internal_population[i].objective >= ind.objective) { + //now measure + int diff_size = m_internal_population[i].cut_edges->size() + ind.cut_edges->size(); + std::vector output_diff(diff_size,std::numeric_limits::max()); + + set_symmetric_difference(m_internal_population[i].cut_edges->begin(), + m_internal_population[i].cut_edges->end(), + ind.cut_edges->begin(), + ind.cut_edges->end(), + output_diff.begin()); + + unsigned similarity = 0; + for( unsigned j = 0; j < output_diff.size(); j++) { + if(output_diff[j] < std::numeric_limits::max()) { + similarity++; + } else { + break; + } + } - if(output) { - m_filebuffer_string << m_global_timer.elapsed() << " " << ind.cut_edges->size()/2 << std::endl; - m_time_stamp++; + if( similarity < max_similarity) { + max_similarity = similarity; + max_similarity_idx = i; } -} + } + } -void population::insert(graph_access & G, Individuum & ind) { + delete[] (m_internal_population[max_similarity_idx].partition_map); + delete m_internal_population[max_similarity_idx].cut_edges; - m_no_partition_calls++; - if(m_internal_population.size() < m_population_size) { - m_internal_population.push_back(ind); - } else { - EdgeWeight worst_objective = 0; - for( unsigned i = 0; i < m_internal_population.size(); i++) { - if(m_internal_population[i].objective > worst_objective) { - worst_objective = m_internal_population[i].objective; - } - } - if(ind.objective > worst_objective ) { - delete[] (ind.partition_map); - delete ind.cut_edges; - return; // do nothing - } - //else measure similarity - unsigned max_similarity = std::numeric_limits::max(); - unsigned max_similarity_idx = 0; - for( unsigned i = 0; i < m_internal_population.size(); i++) { - if(m_internal_population[i].objective >= ind.objective) { - //now measure - int diff_size = m_internal_population[i].cut_edges->size() + ind.cut_edges->size(); - std::vector output_diff(diff_size,std::numeric_limits::max()); - - set_symmetric_difference(m_internal_population[i].cut_edges->begin(), - m_internal_population[i].cut_edges->end(), - ind.cut_edges->begin(), - ind.cut_edges->end(), - output_diff.begin()); - - unsigned similarity = 0; - for( unsigned j = 0; j < output_diff.size(); j++) { - if(output_diff[j] < std::numeric_limits::max()) { - similarity++; - } else { - break; - } - } - - if( similarity < max_similarity) { - max_similarity = similarity; - max_similarity_idx = i; - } - } - } - - delete[] (m_internal_population[max_similarity_idx].partition_map); - delete m_internal_population[max_similarity_idx].cut_edges; - - m_internal_population[max_similarity_idx] = ind; - } + m_internal_population[max_similarity_idx] = ind; + } } void population::replace(Individuum & in, Individuum & out) { - //first find it: - for( unsigned i = 0; i < m_internal_population.size(); i++) { - if(m_internal_population[i].partition_map == in.partition_map) { - //found it - delete[] (m_internal_population[i].partition_map); - delete m_internal_population[i].cut_edges; - - m_internal_population[i] = out; - break; - } - } + //first find it: + for( unsigned i = 0; i < m_internal_population.size(); i++) { + if(m_internal_population[i].partition_map == in.partition_map) { + //found it + delete[] (m_internal_population[i].partition_map); + delete m_internal_population[i].cut_edges; + + m_internal_population[i] = out; + break; + } + } } -void population::combine(const PartitionConfig & partition_config, - graph_access & G, - Individuum & first_ind, - Individuum & second_ind, +void population::combine(const PartitionConfig & partition_config, + graph_access & G, + Individuum & first_ind, + Individuum & second_ind, Individuum & output_ind) { - PartitionConfig config = partition_config; - G.resizeSecondPartitionIndex(G.number_of_nodes()); - if( first_ind.objective < second_ind.objective ) { - forall_nodes(G, node) { - G.setPartitionIndex(node, first_ind.partition_map[node]); - G.setSecondPartitionIndex(node, second_ind.partition_map[node]); - - } endfor - } else { - forall_nodes(G, node) { - G.setPartitionIndex(node, second_ind.partition_map[node]); - G.setSecondPartitionIndex(node, first_ind.partition_map[node]); - } endfor - } - - config.combine = true; - config.graph_allready_partitioned = true; - config.no_new_initial_partitioning = true; - - bool coin = false; - if( partition_config.mh_enable_gal_combine ) { - coin = random_functions::nextBool(); - } - - if( coin ) { - gal_combine combine_operator; - combine_operator.perform_gal_combine( config, G); - int* partition_map = new int[G.number_of_nodes()]; - - forall_nodes(G, node) { - partition_map[node] = G.getPartitionIndex(node); - } endfor - - quality_metrics qm; - output_ind.objective = qm.objective(config, G, partition_map); - output_ind.partition_map = partition_map; - output_ind.cut_edges = new std::vector(); - - forall_nodes(G, node) { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if(partition_map[node] != partition_map[target]) { - output_ind.cut_edges->push_back(e); - } - } endfor - } endfor - } else { - createIndividuum(config, G, output_ind, true); - } - std::cout << "objective mh " << output_ind.objective << std::endl; + PartitionConfig config = partition_config; + G.resizeSecondPartitionIndex(G.number_of_nodes()); + if( first_ind.objective < second_ind.objective ) { + forall_nodes(G, node) { + G.setPartitionIndex(node, first_ind.partition_map[node]); + G.setSecondPartitionIndex(node, second_ind.partition_map[node]); + + } endfor +} else { + forall_nodes(G, node) { + G.setPartitionIndex(node, second_ind.partition_map[node]); + G.setSecondPartitionIndex(node, first_ind.partition_map[node]); + } endfor } -void population::combine_cross(const PartitionConfig & partition_config, - graph_access & G, - Individuum & first_ind, - Individuum & output_ind) { - - PartitionConfig config = partition_config; - G.resizeSecondPartitionIndex(G.number_of_nodes()); - - int lowerbound = config.k / 4; - lowerbound = std::max(2, lowerbound); - int kfactor = random_functions::nextInt(lowerbound,4*config.k); - kfactor = std::min( kfactor, (int)G.number_of_nodes()); - - if( config.mh_cross_combine_original_k ) { - MPI_Bcast(&kfactor, 1, MPI_INT, 0, m_communicator); + config.combine = true; + config.graph_allready_partitioned = true; + config.no_new_initial_partitioning = true; + + bool coin = false; + if( partition_config.mh_enable_gal_combine ) { + coin = random_functions::nextBool(); + } + + if( coin ) { + gal_combine combine_operator; + combine_operator.perform_gal_combine( config, G); + int* partition_map = new int[G.number_of_nodes()]; + + forall_nodes(G, node) { + partition_map[node] = G.getPartitionIndex(node); + } endfor + + quality_metrics qm; + output_ind.objective = qm.objective(config, G, partition_map); + output_ind.partition_map = partition_map; + output_ind.cut_edges = new std::vector(); + + forall_nodes(G, node) { + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + if(partition_map[node] != partition_map[target]) { + output_ind.cut_edges->push_back(e); } + } endfor + } endfor + } else { + createIndividuum(config, G, output_ind, true); + } + std::cout << "objective mh " << output_ind.objective << std::endl; +} - unsigned larger_imbalance = random_functions::nextInt(config.epsilon,25); - double epsilon = larger_imbalance/100.0; - - - PartitionConfig cross_config = config; - cross_config.k = kfactor; - cross_config.kaffpa_perfectly_balanced_refinement = false; - cross_config.upper_bound_partition = (1+epsilon)*ceil(partition_config.largest_graph_weight/(double)partition_config.k); - cross_config.refinement_scheduling_algorithm = REFINEMENT_SCHEDULING_ACTIVE_BLOCKS; - cross_config.combine = false; - cross_config.graph_allready_partitioned = false; - - std::ofstream ofs; - std::streambuf* backup = std::cout.rdbuf(); -#ifdef _WIN32 - ofs.open("NUL"); -#else - ofs.open("/dev/null"); -#endif - std::cout.rdbuf(ofs.rdbuf()); - - graph_partitioner partitioner; - partitioner.perform_partitioning(cross_config, G); - - ofs.close(); - std::cout.rdbuf(backup); - - forall_nodes(G, node) { - G.setSecondPartitionIndex(node, G.getPartitionIndex(node)); - G.setPartitionIndex(node, first_ind.partition_map[node]); - } endfor - - config.combine = true; - config.graph_allready_partitioned = true; - config.no_new_initial_partitioning = true; - - createIndividuum(config, G, output_ind, true); - std::cout << "objective cross " << output_ind.objective - << " k " << kfactor - << " imbal " << larger_imbalance - << " impro " << (first_ind.objective - output_ind.objective) << std::endl; - +void population::combine_cross(PartitionConfig const& partition_config, + graph_access& G, + Individuum& first_ind, + Individuum& output_ind) { + if (partition_config.mh_cross_combine_original_k) { + auto communicator_size = 0; + ::kahip::parallel_mh::detail::check_mpi( + MPI_Comm_size(m_communicator, &communicator_size), m_communicator, + "MPI_Comm_size(evolutionary cross combine)"); + if (communicator_size <= 0) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, "evolutionary cross combine", + "MPI returned an invalid evolutionary communicator size"); + } + // Evolutionary workers enter combine_cross asynchronously. A conditional + // collective here cannot be matched by peers and therefore cannot be made + // safe without changing the algorithm's scheduling semantics. + if (communicator_size > 1) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, "evolutionary cross combine", + "original-k cross combine is incompatible with asynchronous " + "multi-rank entry"); + } + } + + PartitionConfig config = partition_config; + G.resizeSecondPartitionIndex(G.number_of_nodes()); + + int lowerbound = config.k / 4; + lowerbound = std::max(2, lowerbound); + int kfactor = random_functions::nextInt(lowerbound, 4 * config.k); + kfactor = std::min(kfactor, (int)G.number_of_nodes()); + + unsigned larger_imbalance = random_functions::nextInt(config.epsilon, 25); + double epsilon = larger_imbalance / 100.0; + + PartitionConfig cross_config = config; + cross_config.k = kfactor; + cross_config.kaffpa_perfectly_balanced_refinement = false; + cross_config.upper_bound_partition = + (1 + epsilon) * + ceil(partition_config.largest_graph_weight / (double)cross_config.k); + cross_config.refinement_scheduling_algorithm = + REFINEMENT_SCHEDULING_ACTIVE_BLOCKS; + cross_config.combine = false; + cross_config.graph_allready_partitioned = false; + + graph_partitioner partitioner; + { + auto suppression = scoped_output_suppression{}; + partitioner.perform_partitioning(cross_config, G); + } + + forall_nodes(G, node) { + G.setSecondPartitionIndex(node, G.getPartitionIndex(node)); + G.setPartitionIndex(node, first_ind.partition_map[node]); + } endfor + + config.combine = true; + config.graph_allready_partitioned = true; + config.no_new_initial_partitioning = true; + + createIndividuum(config, G, output_ind, true); + std::cout << "objective cross " << output_ind.objective + << " k " << kfactor + << " imbal " << larger_imbalance + << " impro " << (first_ind.objective - output_ind.objective) << std::endl; } void population::mutate_random( const PartitionConfig & partition_config, graph_access & G, Individuum & first_ind) { - int number = random_functions::nextInt(0,5); + int number = random_functions::nextInt(0,5); - PartitionConfig config = partition_config; - config.combine = false; - config.graph_allready_partitioned = true; - get_random_individuum(first_ind); + PartitionConfig config = partition_config; + config.combine = false; + config.graph_allready_partitioned = true; + get_random_individuum(first_ind); - if(number < 5) { - forall_nodes(G, node) { - G.setPartitionIndex(node, first_ind.partition_map[node]); - } endfor + if(number < 5) { + forall_nodes(G, node) { + G.setPartitionIndex(node, first_ind.partition_map[node]); + } endfor - config.no_new_initial_partitioning = true; - createIndividuum( config, G, first_ind, true); + config.no_new_initial_partitioning = true; + createIndividuum( config, G, first_ind, true); - } else { - forall_nodes(G, node) { - G.setPartitionIndex(node, first_ind.partition_map[node]); - } endfor + } else { + forall_nodes(G, node) { + G.setPartitionIndex(node, first_ind.partition_map[node]); + } endfor - config.graph_allready_partitioned = false; - createIndividuum( config, G, first_ind, true); - } + config.graph_allready_partitioned = false; + createIndividuum( config, G, first_ind, true); + } } void population::extinction( ) { - for( unsigned i = 0; i < m_internal_population.size(); i++) { - delete[] m_internal_population[i].partition_map; - delete m_internal_population[i].cut_edges; - } + for( unsigned i = 0; i < m_internal_population.size(); i++) { + delete[] m_internal_population[i].partition_map; + delete m_internal_population[i].cut_edges; + } - m_internal_population.clear(); - m_internal_population.resize(0); + m_internal_population.clear(); + m_internal_population.resize(0); } void population::get_two_random_individuals(Individuum & first, Individuum & second) { - int first_idx = random_functions::nextInt(0, m_internal_population.size()-1); - first = m_internal_population[first_idx]; + int first_idx = random_functions::nextInt(0, m_internal_population.size()-1); + first = m_internal_population[first_idx]; - int second_idx = random_functions::nextInt(0, m_internal_population.size()-1); - while( first_idx == second_idx ) { - second_idx = random_functions::nextInt(0, m_internal_population.size()-1); - } + int second_idx = random_functions::nextInt(0, m_internal_population.size()-1); + while( first_idx == second_idx ) { + second_idx = random_functions::nextInt(0, m_internal_population.size()-1); + } - second = m_internal_population[second_idx]; + second = m_internal_population[second_idx]; } void population::get_one_individual_tournament(Individuum & first) { - Individuum one, two; - get_two_random_individuals(one, two); - first = one.objective < two.objective ? one : two; + Individuum one, two; + get_two_random_individuals(one, two); + first = one.objective < two.objective ? one : two; } void population::get_two_individuals_tournament(Individuum & first, Individuum & second) { - Individuum one, two; - get_two_random_individuals(one, two); - first = one.objective < two.objective? one : two; + Individuum one, two; + get_two_random_individuals(one, two); + first = one.objective < two.objective? one : two; - get_two_random_individuals(one, two); - second = one.objective < two.objective ? one : two; + get_two_random_individuals(one, two); + second = one.objective < two.objective ? one : two; - if( first.objective == second.objective) { - second = one.objective >= two.objective? one : two; - } + if( first.objective == second.objective) { + second = one.objective >= two.objective? one : two; + } } void population::get_random_individuum(Individuum & ind) { - int idx = random_functions::nextInt(0, m_internal_population.size()-1); - ind = m_internal_population[idx]; + int idx = random_functions::nextInt(0, m_internal_population.size()-1); + ind = m_internal_population[idx]; } void population::get_best_individuum(Individuum & ind) { - EdgeWeight min_objective = std::numeric_limits::max(); - unsigned idx = 0; - - for( unsigned i = 0; i < m_internal_population.size(); i++) { - if((EdgeWeight)m_internal_population[i].objective < min_objective) { - min_objective = m_internal_population[i].objective; - idx = i; - } - } + EdgeWeight min_objective = std::numeric_limits::max(); + unsigned idx = 0; - ind = m_internal_population[idx]; + for( unsigned i = 0; i < m_internal_population.size(); i++) { + if((EdgeWeight)m_internal_population[i].objective < min_objective) { + min_objective = m_internal_population[i].objective; + idx = i; + } + } + + ind = m_internal_population[idx]; } bool population::is_full() { - return m_internal_population.size() == m_population_size; + return m_internal_population.size() == m_population_size; } void population::apply_fittest( graph_access & G, EdgeWeight & objective ) { - EdgeWeight min_objective = std::numeric_limits::max(); - double best_balance = std::numeric_limits::max(); - unsigned idx = 0; - - quality_metrics qm; - for( unsigned i = 0; i < m_internal_population.size(); i++) { - forall_nodes(G, node) { - G.setPartitionIndex(node, m_internal_population[i].partition_map[node]); - } endfor - double cur_balance = qm.balance(G); - if((EdgeWeight)m_internal_population[i].objective < min_objective - || ((EdgeWeight)m_internal_population[i].objective == min_objective && cur_balance < best_balance)) { - min_objective = m_internal_population[i].objective; - idx = i; - best_balance = cur_balance; - } - } + EdgeWeight min_objective = std::numeric_limits::max(); + double best_balance = std::numeric_limits::max(); + unsigned idx = 0; + + quality_metrics qm; + for( unsigned i = 0; i < m_internal_population.size(); i++) { + forall_nodes(G, node) { + G.setPartitionIndex(node, m_internal_population[i].partition_map[node]); + } endfor + double cur_balance = qm.balance(G); + if((EdgeWeight)m_internal_population[i].objective < min_objective +|| ((EdgeWeight)m_internal_population[i].objective == min_objective && cur_balance < best_balance)) { + min_objective = m_internal_population[i].objective; + idx = i; + best_balance = cur_balance; +} + } - forall_nodes(G, node) { - G.setPartitionIndex(node, m_internal_population[idx].partition_map[node]); - } endfor + forall_nodes(G, node) { + G.setPartitionIndex(node, m_internal_population[idx].partition_map[node]); + } endfor - objective = min_objective; + objective = min_objective; } void population::print() { - int rank; - MPI_Comm_rank( m_communicator, &rank); - - std::cout << "rank " << rank << " fingerprint "; + auto rank = -1; + ::kahip::parallel_mh::detail::check_mpi( + MPI_Comm_rank(m_communicator, &rank), m_communicator, + "MPI_Comm_rank(evolutionary population print)"); + + std::cout << "rank " << rank << " fingerprint "; - for( unsigned i = 0; i < m_internal_population.size(); i++) { - std::cout << m_internal_population[i].objective << " "; - } + for( unsigned i = 0; i < m_internal_population.size(); i++) { + std::cout << m_internal_population[i].objective << " "; + } - std::cout << std::endl; + std::cout << std::endl; } void population::write_log(std::string & filename) { - std::ofstream f(filename.c_str()); - f << m_filebuffer_string.str(); - f.close(); + std::ofstream f(filename.c_str()); + f << m_filebuffer_string.str(); + f.close(); } - diff --git a/lib/parallel_mh/population.h b/lib/parallel_mh/population.h index bedf972a..baf6401a 100644 --- a/lib/parallel_mh/population.h +++ b/lib/parallel_mh/population.h @@ -1,5 +1,5 @@ /****************************************************************************** - * population.h + * population.h * * * Source of KaHIP -- Karlsruhe High Quality Partitioning. * Christian Schulz @@ -14,11 +14,10 @@ #include "data_structure/graph_access.h" #include "partition_config.h" #include "timer.h" - struct Individuum { - int* partition_map; - EdgeWeight objective; - std::vector* cut_edges; //sorted + int* partition_map = nullptr; + EdgeWeight objective = 0; + std::vector* cut_edges = nullptr; //sorted }; struct ENC { @@ -26,77 +25,76 @@ struct ENC { }; class population { - public: - population( MPI_Comm comm, const PartitionConfig & config ); - virtual ~population(); +public: + population( MPI_Comm comm, const PartitionConfig & config ); + virtual ~population(); - void createIndividuum(const PartitionConfig & config, - graph_access & G, - Individuum & ind, - bool output); + void createIndividuum(const PartitionConfig & config, + graph_access & G, + Individuum & ind, + bool output); - void combine(const PartitionConfig & config, - graph_access & G, - Individuum & first_ind, - Individuum & second_ind, - Individuum & output_ind); + void combine(const PartitionConfig & config, + graph_access & G, + Individuum & first_ind, + Individuum & second_ind, + Individuum & output_ind); - void combine_cross(const PartitionConfig & partition_config, - graph_access & G, - Individuum & first_ind, - Individuum & output_ind); + void combine_cross(const PartitionConfig & partition_config, + graph_access & G, + Individuum & first_ind, + Individuum & output_ind); - void mutate_random(const PartitionConfig & partition_config, - graph_access & G, - Individuum & first_ind); + void mutate_random(const PartitionConfig & partition_config, + graph_access & G, + Individuum & first_ind); - void insert(graph_access & G, Individuum & ind); + void insert(graph_access & G, Individuum & ind); - void set_pool_size(int size); + void set_pool_size(int size); - void extinction(); + void extinction(); - void get_two_random_individuals(Individuum & first, Individuum & second); - - void get_one_individual_tournament(Individuum & first); + void get_two_random_individuals(Individuum & first, Individuum & second); - void get_two_individuals_tournament(Individuum & first, Individuum & second); + void get_one_individual_tournament(Individuum & first); - void replace(Individuum & in, Individuum & out); + void get_two_individuals_tournament(Individuum & first, Individuum & second); - void get_random_individuum(Individuum & ind); + void replace(Individuum & in, Individuum & out); - void get_best_individuum(Individuum & ind); + void get_random_individuum(Individuum & ind); - bool is_full(); + void get_best_individuum(Individuum & ind); - void apply_fittest( graph_access & G, EdgeWeight & objective); + bool is_full(); - unsigned size() { return m_internal_population.size(); } - - void print(); + void apply_fittest( graph_access & G, EdgeWeight & objective); - void write_log(std::string & filename); + unsigned size() { return m_internal_population.size(); } + void print(); - private: + void write_log(std::string & filename); - unsigned m_no_partition_calls; - unsigned m_population_size; - std::vector m_internal_population; - std::vector< std::vector< unsigned int > > m_vertex_ENCs; - std::vector< ENC > m_ENCs; - int m_num_NCs; - int m_num_NCs_computed; - int m_num_ENCs; - int m_time_stamp; +private: - MPI_Comm m_communicator; + unsigned m_no_partition_calls = 0; + unsigned m_population_size = 0; + std::vector m_internal_population; + std::vector< std::vector< unsigned int > > m_vertex_ENCs; + std::vector< ENC > m_ENCs; - std::stringstream m_filebuffer_string; - timer m_global_timer; -}; + int m_num_NCs = 0; + int m_num_NCs_computed = 0; + int m_num_ENCs = 0; + int m_time_stamp = 0; + MPI_Comm m_communicator = MPI_COMM_NULL; + + std::stringstream m_filebuffer_string; + timer m_global_timer; +}; #endif /* end of include guard: POPULATION_AEFH46G6 */ diff --git a/lib/parallel_mh/population_size_broadcast.h b/lib/parallel_mh/population_size_broadcast.h new file mode 100644 index 00000000..6e02a53b --- /dev/null +++ b/lib/parallel_mh/population_size_broadcast.h @@ -0,0 +1,86 @@ +#pragma once + +#include + +#include +#include +#include +#include + +#include "parallel_mh/evolutionary_collectives.h" + +namespace kahip::parallel_mh { +struct quick_start_plan final { + unsigned local_creations = 0; + unsigned diversifications = 0; + + auto operator==(quick_start_plan const&) const -> bool = default; +}; + +[[nodiscard]] constexpr auto clamp_population_size( + int estimate, + bool easy_construction) noexcept -> int { + return std::clamp(estimate, 3, easy_construction ? 50 : 100); +} + +[[nodiscard]] constexpr auto quick_start_population_plan( + unsigned population_size, + int communicator_size) noexcept -> std::optional { + if (communicator_size <= 0) { + return std::nullopt; + } + + auto const pool = static_cast(population_size); + auto const peers = static_cast(communicator_size); + auto const local_share = pool / peers + (pool % peers != 0 ? 1 : 0); + auto const local_creations = std::max(local_share - 1, 0); + auto const diversifications = + std::max(pool - local_creations, std::int64_t{0}); + return quick_start_plan{ + .local_creations = static_cast(local_creations), + .diversifications = static_cast(diversifications), + }; +} + +[[nodiscard]] inline auto estimate_population_size( + double time_limit, + double initial_population_fraction, + double elapsed_partition_time, + bool easy_construction) noexcept -> std::optional { + if (!std::isfinite(time_limit) || time_limit < 0.0 || + !std::isfinite(initial_population_fraction) || + initial_population_fraction <= 0.0 || + !std::isfinite(elapsed_partition_time) || elapsed_partition_time < 0.0) { + return std::nullopt; + } + + auto const maximum = easy_construction ? 50 : 100; + if (elapsed_partition_time == 0.0) { + return maximum; + } + + // Preserve the upstream formula and its ceiling, but compare against the + // bounded population domain before converting a potentially huge value to + // int. Long double also avoids avoidable overflow in the intermediate + // divisions on implementations where it has a wider exponent range. + auto const estimate = + (static_cast(time_limit) / + static_cast(initial_population_fraction)) / + static_cast(elapsed_partition_time); + if (!std::isfinite(estimate) || estimate >= maximum) { + return maximum; + } + return clamp_population_size(static_cast(std::ceil(estimate)), + easy_construction); +} + +[[nodiscard]] inline auto broadcast_population_size( + MPI_Comm communicator, + int root_estimate, + bool easy_construction) noexcept -> int { + auto population_size = root_estimate; + detail::check_mpi(MPI_Bcast(&population_size, 1, MPI_INT, 0, communicator), + communicator, "MPI_Bcast(population size)"); + return clamp_population_size(population_size, easy_construction); +} +} // namespace kahip::parallel_mh diff --git a/lib/partition/initial_partitioning/bipartition.cpp b/lib/partition/initial_partitioning/bipartition.cpp index 4f0a5549..dc933da2 100644 --- a/lib/partition/initial_partitioning/bipartition.cpp +++ b/lib/partition/initial_partitioning/bipartition.cpp @@ -6,9 +6,15 @@ *****************************************************************************/ #include +#include +#include #include +#include +#include + #include "bipartition.h" #include "data_structure/priority_queues/maxNodeHeap.h" +#include "partition/initial_partitioning/bipartition_candidate.h" #include "quality_metrics.h" #include "random_functions.h" #include "timer.h" @@ -81,14 +87,40 @@ void bipartition::initial_partition( const PartitionConfig & config, timer t; t.restart(); - unsigned iterations = config.bipartition_tries; - EdgeWeight best_cut = std::numeric_limits::max(); - int best_load = std::numeric_limits::max(); + + auto const targets = + kahip::initial_partitioning::validated_bipartition_targets( + config.target_weights); + if(!targets) { + throw std::invalid_argument( + "bipartition requires two nonnegative target weights"); + } + if(config.bipartition_tries <= 0) { + throw std::invalid_argument( + "bipartition requires at least one candidate"); + } + if(config.bipartition_algorithm != BIPARTITION_BFS && + config.bipartition_algorithm != BIPARTITION_FM) { + throw std::invalid_argument( + "bipartition requires a valid growth algorithm"); + } + + if(G.number_of_nodes() == 0) { + G.set_partition_count(2); + PRINT(std::cout << "bipartition took " << t.elapsed() << std::endl;) + return; + } + + auto const iterations = static_cast(config.bipartition_tries); + auto const requires_two_nonempty_blocks = G.number_of_nodes() >= 2; + std::optional + best_candidate; + std::vector best_partition(G.number_of_nodes()); for( unsigned i = 0; i < iterations; i++) { if(config.bipartition_algorithm == BIPARTITION_BFS) { grow_regions_bfs(config, G); - } else if( config.bipartition_algorithm == BIPARTITION_FM) { + } else { grow_regions_fm(config, G); } @@ -99,30 +131,56 @@ void bipartition::initial_partition( const PartitionConfig & config, quality_metrics qm; EdgeWeight curcut = qm.edge_cut(G); - int lhs_block_weight = 0; - int rhs_block_weight = 0; + if(curcut < 0) { + throw std::logic_error( + "bipartition produced a negative edge cut"); + } + + std::uint64_t lhs_block_weight = 0; + std::uint64_t rhs_block_weight = 0; + std::uint64_t lhs_vertices = 0; + std::uint64_t rhs_vertices = 0; + bool partition_ids_are_valid = true; forall_nodes(G, node) { if(G.getPartitionIndex(node) == 0) { lhs_block_weight += G.getNodeWeight(node); - } else { + ++lhs_vertices; + } else if(G.getPartitionIndex(node) == 1) { rhs_block_weight += G.getNodeWeight(node); + ++rhs_vertices; + } else { + partition_ids_are_valid = false; } } endfor - int lhs_overload = std::max(lhs_block_weight - config.target_weights[0],0); - int rhs_overload = std::max(rhs_block_weight - config.target_weights[1],0); - if(curcut < best_cut || (curcut == best_cut && lhs_overload + rhs_block_weight < best_load) ) { - //store it - best_cut = curcut; - best_load = lhs_overload + rhs_overload; - + auto const candidate = + kahip::initial_partitioning::make_bipartition_candidate( + static_cast(curcut), + lhs_block_weight, + rhs_block_weight, + *targets, + lhs_vertices, + rhs_vertices, + partition_ids_are_valid, + requires_two_nonempty_blocks, + i); + if(!best_candidate || + kahip::initial_partitioning::is_better_bipartition_candidate( + candidate, *best_candidate)) { + best_candidate = candidate; forall_nodes(G, n) { - partition_map[n] = G.getPartitionIndex(n); + best_partition[n] = G.getPartitionIndex(n); } endfor - } + } + + } + if(!best_candidate || !best_candidate->valid_blocks) { + throw std::runtime_error( + "bipartition failed to produce two valid nonempty blocks"); } + std::ranges::copy(best_partition, partition_map); PRINT(std::cout << "bipartition took " << t.elapsed() << std::endl;) } @@ -283,7 +341,10 @@ void bipartition::grow_regions_bfs(const PartitionConfig & config, graph_access G.setPartitionIndex(node, 1); } endfor - NodeID nodes_left = G.number_of_nodes()-1; + // The queued start vertex has not been assigned yet. Count it until it + // is removed from the queue so the RHS-preservation guard observes the + // actual number of unassigned vertices. + NodeID nodes_left = G.number_of_nodes(); //now perform a bfs to get a partition std::queue* bfsqueue = new std::queue; @@ -365,7 +426,9 @@ void bipartition::grow_regions_fm(const PartitionConfig & config, graph_access & G.setPartitionIndex(node, 1); } endfor - NodeID nodes_left = G.number_of_nodes()-1; + // The queued start vertex has not been assigned yet; see + // grow_regions_bfs. + NodeID nodes_left = G.number_of_nodes(); //now perform a pseudo dijkstra to get a partition maxNodeHeap* queue = new maxNodeHeap(); diff --git a/lib/partition/initial_partitioning/bipartition.h b/lib/partition/initial_partitioning/bipartition.h index c6676fbc..be8cd63a 100644 --- a/lib/partition/initial_partitioning/bipartition.h +++ b/lib/partition/initial_partitioning/bipartition.h @@ -11,6 +11,8 @@ #include "initial_partitioner.h" class bipartition : public initial_partitioner { + friend struct bipartition_invariant_test_access; + public: bipartition(); virtual ~bipartition(); diff --git a/lib/partition/initial_partitioning/bipartition_candidate.h b/lib/partition/initial_partitioning/bipartition_candidate.h new file mode 100644 index 00000000..0903dc89 --- /dev/null +++ b/lib/partition/initial_partitioning/bipartition_candidate.h @@ -0,0 +1,104 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace kahip::initial_partitioning { +struct bipartition_targets final { + std::uint64_t lhs = 0; + std::uint64_t rhs = 0; + + auto operator==(bipartition_targets const&) const -> bool = default; +}; + +[[nodiscard]] constexpr auto validated_bipartition_targets( + std::span target_weights) noexcept + -> std::optional { + if (target_weights.size() < 2 || target_weights[0] < 0 || + target_weights[1] < 0) { + return std::nullopt; + } + return bipartition_targets{static_cast(target_weights[0]), + static_cast(target_weights[1])}; +} + +[[nodiscard]] constexpr auto overload(std::uint64_t weight, + std::uint64_t target) noexcept + -> std::uint64_t { + return weight > target ? weight - target : 0; +} + +[[nodiscard]] constexpr auto saturating_add(std::uint64_t lhs, + std::uint64_t rhs) noexcept + -> std::uint64_t { + return rhs > std::numeric_limits::max() - lhs + ? std::numeric_limits::max() + : lhs + rhs; +} + +struct bipartition_candidate final { + std::uint64_t cut = 0; + std::uint64_t lhs_weight = 0; + std::uint64_t rhs_weight = 0; + std::uint64_t lhs_overload = 0; + std::uint64_t rhs_overload = 0; + std::uint64_t lhs_vertices = 0; + std::uint64_t rhs_vertices = 0; + std::uint64_t semantic_order = 0; + bool valid_blocks = false; + + [[nodiscard]] constexpr auto total_overload() const noexcept + -> std::uint64_t { + return saturating_add(lhs_overload, rhs_overload); + } + +}; + +[[nodiscard]] constexpr auto make_bipartition_candidate( + std::uint64_t cut, + std::uint64_t lhs_weight, + std::uint64_t rhs_weight, + bipartition_targets targets, + std::uint64_t lhs_vertices, + std::uint64_t rhs_vertices, + bool partition_ids_are_valid, + bool requires_two_nonempty_blocks, + std::uint64_t semantic_order) noexcept -> bipartition_candidate { + auto const valid_blocks = + partition_ids_are_valid && (!requires_two_nonempty_blocks || + (lhs_vertices != 0 && rhs_vertices != 0)); + return bipartition_candidate{ + cut, + lhs_weight, + rhs_weight, + overload(lhs_weight, targets.lhs), + overload(rhs_weight, targets.rhs), + lhs_vertices, + rhs_vertices, + semantic_order, + valid_blocks, + }; +} + +// A valid two-block partition always dominates a malformed candidate. The +// target weights guide region growth; they are not the final global balance +// constraint, which is verified after partitioning. Preserve KaHIP's cut-first +// candidate semantics, use total overload only to break equal-cut candidates, +// and retain the first trial when both quantities tie. This fixes the historic +// rhs-block-weight comparison without changing the established random stream. +[[nodiscard]] constexpr auto is_better_bipartition_candidate( + bipartition_candidate const& challenger, + bipartition_candidate const& incumbent) noexcept -> bool { + if (challenger.valid_blocks != incumbent.valid_blocks) { + return challenger.valid_blocks; + } + auto const selection_key = [](bipartition_candidate const& candidate) { + return std::tuple{candidate.cut, candidate.total_overload(), + candidate.semantic_order}; + }; + return selection_key(challenger) < selection_key(incumbent); +} +} // namespace kahip::initial_partitioning diff --git a/lib/tools/fatal_diagnostics.h b/lib/tools/fatal_diagnostics.h new file mode 100644 index 00000000..82272be1 --- /dev/null +++ b/lib/tools/fatal_diagnostics.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace kahip::diagnostics { +struct sink final { + void (*write)(std::string_view) noexcept; + void (*flush)() noexcept; +}; + +namespace detail { +inline void write_to_stderr(std::string_view message) noexcept { + try { + std::cerr.write(message.data(), + static_cast(message.size())); + std::cerr.put('\n'); + } catch (...) { + // Diagnostics must never replace the fail-fast path. + } +} + +inline void flush_stderr() noexcept { + try { + std::cerr.flush(); + } catch (...) { + // Diagnostics must never replace the fail-fast path. + } +} + +template +concept stream_insertable = requires(std::ostream& output, T&& value) { + { + output << std::forward(value) + } -> std::same_as; +}; + +inline constexpr auto default_sink = sink{ + .write = write_to_stderr, + .flush = flush_stderr, +}; +inline sink const* active_sink = &default_sink; +inline constexpr auto formatting_failure_message = + std::string_view{"fatal diagnostic formatting failed"}; +} // namespace detail + +[[nodiscard]] inline auto exchange_sink_for_testing( + sink const* replacement) noexcept -> sink const* { + auto const* previous = detail::active_sink; + detail::active_sink = + replacement == nullptr ? &detail::default_sink : replacement; + return previous; +} + +template + requires(sizeof...(Fragments) > 0 && + (detail::stream_insertable && ...)) +inline void critical(Fragments&&... fragments) noexcept { + auto const* destination = detail::active_sink; + try { + auto message = std::ostringstream{}; + (message << ... << std::forward(fragments)); + if (message) { + destination->write(message.str()); + } else { + destination->write(detail::formatting_failure_message); + } + } catch (...) { + destination->write(detail::formatting_failure_message); + } + destination->flush(); +} +} // namespace kahip::diagnostics diff --git a/lib/tools/graph_communication.cpp b/lib/tools/graph_communication.cpp deleted file mode 100644 index 37c60ad8..00000000 --- a/lib/tools/graph_communication.cpp +++ /dev/null @@ -1,74 +0,0 @@ -/****************************************************************************** - * graph_communication.cpp - * * - * Source of KaHIP -- Karlsruhe High Quality Partitioning. - * Christian Schulz - *****************************************************************************/ - -#include - -#include "graph_communication.h" - -graph_communication::graph_communication() { - -} - -graph_communication::~graph_communication() { - -} - -void graph_communication::broadcast_graph( graph_access & G, unsigned root) { - int rank; - MPI_Comm_rank(MPI_COMM_WORLD, &rank); - - //first B-Cast number of nodes and number of edges - unsigned number_of_nodes = 0; - unsigned number_of_edges = 0; - - std::vector< int > buffer(2,0); - if(rank == (int)root) { - buffer[0] = G.number_of_nodes(); - buffer[1] = G.number_of_edges(); - } - MPI_Bcast(&buffer[0], 2, MPI_INT, root, MPI_COMM_WORLD); - - number_of_nodes = buffer[0]; - number_of_edges = buffer[1]; - - kahip_idx* xadj; - kahip_idx* adjncy; - int* vwgt; - kahip_idx* adjwgt; - - if( rank == (int)root) { - xadj = G.UNSAFE_metis_style_xadj_array(); - adjncy = G.UNSAFE_metis_style_adjncy_array(); - - vwgt = G.UNSAFE_metis_style_vwgt_array(); - adjwgt = G.UNSAFE_metis_style_adjwgt_array(); - } else { - xadj = new kahip_idx[number_of_nodes+1]; - adjncy = new kahip_idx[number_of_edges]; - - vwgt = new int[number_of_nodes]; - adjwgt = new kahip_idx[number_of_edges]; - } - -#ifdef KAHIP_64BIT - MPI_Datatype mpi_kahip_idx = MPI_INT64_T; -#else - MPI_Datatype mpi_kahip_idx = MPI_INT; -#endif - MPI_Bcast(xadj, number_of_nodes+1, mpi_kahip_idx, root, MPI_COMM_WORLD); - MPI_Bcast(adjncy, number_of_edges , mpi_kahip_idx, root, MPI_COMM_WORLD); - MPI_Bcast(vwgt, number_of_nodes , MPI_INT, root, MPI_COMM_WORLD); - MPI_Bcast(adjwgt, number_of_edges , mpi_kahip_idx, root, MPI_COMM_WORLD); - - G.build_from_metis_weighted( number_of_nodes, xadj, adjncy, vwgt, adjwgt); - - delete[] xadj; - delete[] adjncy; - delete[] vwgt; - delete[] adjwgt; - -} diff --git a/lib/tools/graph_communication.h b/lib/tools/graph_communication.h deleted file mode 100644 index e52d2e6e..00000000 --- a/lib/tools/graph_communication.h +++ /dev/null @@ -1,23 +0,0 @@ -/****************************************************************************** - * graph_communication.h - * * - * Source of KaHIP -- Karlsruhe High Quality Partitioning. - * Christian Schulz - *****************************************************************************/ - -#ifndef GRAPH_COMMUNICATION_J5Q2P80G -#define GRAPH_COMMUNICATION_J5Q2P80G - -#include "data_structure/graph_access.h" - -class graph_communication { -public: - graph_communication(); - virtual ~graph_communication(); - - void broadcast_graph( graph_access & G, unsigned root); - -}; - - -#endif /* end of include guard: GRAPH_COMMUNICATION_J5Q2P80G */ diff --git a/lib/tools/mpi_tools.cpp b/lib/tools/mpi_tools.cpp deleted file mode 100644 index 556d104f..00000000 --- a/lib/tools/mpi_tools.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/****************************************************************************** - * mpi_tools.cpp - * * - * Source of KaHIP -- Karlsruhe High Quality Partitioning. - * Christian Schulz - *****************************************************************************/ - -#include -#include - -#include "mpi_tools.h" - -mpi_tools::mpi_tools() { - -} - -mpi_tools::~mpi_tools() { - - -} - -//void mpi_tools::non_active_wait_for_root() { - //int rank, size; - //MPI_Comm_rank( MPI_COMM_WORLD, &rank); - //MPI_Comm_size( MPI_COMM_WORLD, &size); - - //int MASTER = 0; - - //if(rank == MASTER) { - ////wake up call - //bool wakeup = true; - //for( int to = 1; to < size; to++) { - //MPI_Send(&wakeup, 1, MPI_BOOL, to, 0, MPI_COMM_WORLD); - //} - //} else { - ////non-busy waiting: - //bool stop = false; - //do { - //usleep(5000); - //stop = MPI::COMM_WORLD.Iprobe(MASTER,0); - //} while(!stop); - - //bool wakeup = true; - //MPI::COMM_WORLD.Recv(&wakeup, 1, MPI::BOOL, MASTER, 0); - //} -//} - diff --git a/lib/tools/mpi_tools.h b/lib/tools/mpi_tools.h deleted file mode 100644 index 9ddb9867..00000000 --- a/lib/tools/mpi_tools.h +++ /dev/null @@ -1,21 +0,0 @@ -/****************************************************************************** - * mpi_tools.h - * * - * Source of KaHIP -- Karlsruhe High Quality Partitioning. - * Christian Schulz - *****************************************************************************/ - -#ifndef MPI_TOOLS_HMESDXF2 -#define MPI_TOOLS_HMESDXF2 - - -class mpi_tools { -public: - mpi_tools(); - virtual ~mpi_tools(); - - //static void non_active_wait_for_root(); -}; - - -#endif /* end of include guard: MPI_TOOLS_HMESDXF2 */ diff --git a/lib/tools/random_functions.h b/lib/tools/random_functions.h index e88ec948..466816d5 100644 --- a/lib/tools/random_functions.h +++ b/lib/tools/random_functions.h @@ -150,8 +150,8 @@ class random_functions { } static bool nextBool() { - std::uniform_int_distribution A(0,1); - return (bool) A(m_mt); + std::bernoulli_distribution bernoulli(0.5); + return bernoulli(m_mt); } diff --git a/lib/version.h b/lib/version/version.h similarity index 100% rename from lib/version.h rename to lib/version/version.h diff --git a/misc/example_library_call/interface_test.cpp b/misc/example_library_call/interface_test.cpp index aeace6be..824ef69d 100644 --- a/misc/example_library_call/interface_test.cpp +++ b/misc/example_library_call/interface_test.cpp @@ -5,37 +5,36 @@ * *****************************************************************************/ +#include #include -#include +#include #include "kaHIP_interface.h" -int main(int argn, char **argv) { +int main() { std::cout << "partitioning graph from the manual" << std::endl; int n = 5; - kahip_idx* xadj = new kahip_idx[6]; - xadj[0] = 0; xadj[1] = 2; xadj[2] = 5; xadj[3] = 7; xadj[4] = 9; xadj[5] = 12; - - kahip_idx* adjncy = new kahip_idx[12]; - adjncy[0] = 1; adjncy[1] = 4; adjncy[2] = 0; adjncy[3] = 2; adjncy[4] = 4; adjncy[5] = 1; - adjncy[6] = 3; adjncy[7] = 2; adjncy[8] = 4; adjncy[9] = 0; adjncy[10] = 1; adjncy[11] = 3; + std::array xadj{0, 2, 5, 7, 9, 12}; + std::array adjncy{1, 4, 0, 2, 4, 1, + 3, 2, 4, 0, 1, 3}; double imbalance = 0.03; - int* part = new int[n]; + std::vector part(static_cast(n)); kahip_idx edge_cut = 0; int nparts = 2; - int* vwgt = NULL; - kahip_idx* adjcwgt = NULL; + int* vwgt = nullptr; + kahip_idx* adjcwgt = nullptr; //void kaffpa(int* n, int* vwgt, int* xadj, //int* adjcwgt, int* adjncy, int* nparts, //double* imbalance, bool suppress_output, int seed, int mode, //int* edgecut, int* part); - kaffpa(&n, vwgt, xadj, adjcwgt, adjncy, &nparts, &imbalance, false, 0, ECO, & edge_cut, part); + kaffpa(&n, vwgt, xadj.data(), adjcwgt, adjncy.data(), &nparts, + &imbalance, false, 0, ECO, &edge_cut, part.data()); std::cout << "edge cut " << edge_cut << std::endl; @@ -47,15 +46,14 @@ int main(int argn, char **argv) { //bool suppress_output, int seed, //int* edgecut, int* qap, int* part); - int* hierarchy_parameter = new int[2]; - int* distance_parameter = new int[2]; - hierarchy_parameter[0] = 2; - hierarchy_parameter[1] = 2; - distance_parameter[0] = 1; - distance_parameter[1] = 100; + std::array hierarchy_parameter{2, 2}; + std::array distance_parameter{1, 100}; int qap = 0; - process_mapping(&n, vwgt, xadj, adjcwgt, adjncy, hierarchy_parameter, distance_parameter, 2, STRONG, MAPMODE_MULTISECTION, &imbalance, false, 0, & edge_cut, & qap, part); + process_mapping(&n, vwgt, xadj.data(), adjcwgt, adjncy.data(), + hierarchy_parameter.data(), distance_parameter.data(), + 2, STRONG, MAPMODE_MULTISECTION, &imbalance, false, 0, + &edge_cut, &qap, part.data()); std::cout << "edge cut " << edge_cut << std::endl; std::cout << "qap " << qap << std::endl; diff --git a/misc/example_parhip_call/CMakeLists.txt b/misc/example_parhip_call/CMakeLists.txt index 870902ac..6f70ff46 100644 --- a/misc/example_parhip_call/CMakeLists.txt +++ b/misc/example_parhip_call/CMakeLists.txt @@ -1,17 +1,16 @@ -cmake_minimum_required(VERSION 3.10) -project(parhip_example CXX) +cmake_minimum_required(VERSION 4.0...4.3) +project(parhip_example LANGUAGES CXX) -find_package(MPI REQUIRED) +set(CMAKE_CXX_SCAN_FOR_MODULES OFF) -# Adjust this path to where KaHIP was installed or built -set(KAHIP_BUILD_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../build" CACHE PATH "Path to KaHIP build directory") +find_package(PkgConfig REQUIRED) +pkg_check_modules( + parhip + REQUIRED + IMPORTED_TARGET + parhip_interface +) add_executable(parhip_test parhip_test.cpp) -target_include_directories(parhip_test PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/../../parallel/parallel_src/interface - ${MPI_CXX_INCLUDE_DIRS} -) -target_link_libraries(parhip_test PRIVATE - ${KAHIP_BUILD_DIR}/parallel/parallel_src/libparhip_interface.so - ${MPI_CXX_LIBRARIES} -) +target_compile_features(parhip_test PRIVATE cxx_std_23) +target_link_libraries(parhip_test PRIVATE PkgConfig::parhip) diff --git a/parallel/modified_kahip/CMakeLists.txt b/parallel/modified_kahip/CMakeLists.txt index 7795ddcc..0e83d29d 100644 --- a/parallel/modified_kahip/CMakeLists.txt +++ b/parallel/modified_kahip/CMakeLists.txt @@ -1,80 +1,191 @@ -include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/lib) -include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/lib/tools) -include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/lib/partition) -include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/lib/io) -include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement) -include_directories(${MPI_CXX_INCLUDE_PATH}) +set( + MODIFIED_KAHIP_HEADER_BASE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/lib + ${CMAKE_CURRENT_SOURCE_DIR}/lib/tools + ${CMAKE_CURRENT_SOURCE_DIR}/lib/partition + ${CMAKE_CURRENT_SOURCE_DIR}/lib/io + ${CMAKE_CURRENT_SOURCE_DIR}/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement +) + +function(kahip_configure_modified_object target) + kahip_add_header_root_file_sets( + ${target} + modified_header_root + ${MODIFIED_KAHIP_HEADER_BASE_DIRS} + ) + target_link_libraries( + ${target} + PUBLIC kahip_options MPI::MPI_CXX + PRIVATE kahip_warnings + ) +endfunction() + +file( + GLOB_RECURSE MODIFIED_KAHIP_CORE_HEADERS + CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/app/*.h" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/*.h" +) +list( + FILTER MODIFIED_KAHIP_CORE_HEADERS + EXCLUDE + REGEX "/lib/parallel_mh/" +) +file( + GLOB_RECURSE MODIFIED_KAHIP_COLLECTIVE_HEADERS + CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/lib/parallel_mh/*.h" +) set(LIBMODIFIED_KAFFPA_SOURCE_FILES - lib/data_structure/graph_hierarchy.cpp - lib/algorithms/strongly_connected_components.cpp - lib/algorithms/topological_sort.cpp - lib/io/graph_io.cpp - lib/tools/quality_metrics.cpp - lib/tools/random_functions.cpp - lib/tools/graph_extractor.cpp - lib/tools/misc.cpp - lib/tools/partition_snapshooter.cpp - lib/partition/graph_partitioner.cpp - lib/partition/w_cycles/wcycle_partitioner.cpp - lib/partition/coarsening/coarsening.cpp - lib/partition/coarsening/contraction.cpp - lib/partition/coarsening/edge_rating/edge_ratings.cpp - lib/partition/coarsening/matching/matching.cpp - lib/partition/coarsening/matching/random_matching.cpp - lib/partition/coarsening/matching/gpa/path.cpp - lib/partition/coarsening/matching/gpa/gpa_matching.cpp - lib/partition/coarsening/matching/gpa/path_set.cpp - lib/partition/coarsening/clustering/node_ordering.cpp - lib/partition/coarsening/clustering/size_constraint_label_propagation.cpp - lib/partition/initial_partitioning/initial_partitioning.cpp - lib/partition/initial_partitioning/initial_partitioner.cpp - lib/partition/initial_partitioning/initial_partition_bipartition.cpp - lib/partition/initial_partitioning/initial_refinement/initial_refinement.cpp - lib/partition/initial_partitioning/bipartition.cpp - lib/partition/uncoarsening/uncoarsening.cpp - lib/partition/uncoarsening/separator/vertex_separator_algorithm.cpp - lib/partition/uncoarsening/separator/vertex_separator_flow_solver.cpp - lib/partition/uncoarsening/refinement/cycle_improvements/greedy_neg_cycle.cpp - lib/partition/uncoarsening/refinement/cycle_improvements/problem_factory.cpp - lib/partition/uncoarsening/refinement/cycle_improvements/augmented_Qgraph.cpp - lib/partition/uncoarsening/refinement/mixed_refinement.cpp - lib/partition/uncoarsening/refinement/label_propagation_refinement/label_propagation_refinement.cpp - lib/partition/uncoarsening/refinement/refinement.cpp - lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/two_way_fm.cpp - lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/two_way_flow_refinement.cpp - lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/boundary_bfs.cpp - lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/flow_solver.cpp - lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/edge_cut_flow_solver.cpp - lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/timer.cpp - lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/most_balanced_minimum_cuts/most_balanced_minimum_cuts.cpp - lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_refinement.cpp - lib/partition/uncoarsening/refinement/quotient_graph_refinement/complete_boundary.cpp - lib/partition/uncoarsening/refinement/quotient_graph_refinement/partial_boundary.cpp - lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/quotient_graph_scheduling.cpp - lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/simple_quotient_graph_scheduler.cpp - lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/active_block_quotient_graph_scheduler.cpp - lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement.cpp - lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_core.cpp - lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_commons.cpp - lib/partition/uncoarsening/refinement/cycle_improvements/augmented_Qgraph_fabric.cpp - lib/partition/uncoarsening/refinement/cycle_improvements/advanced_models.cpp - lib/partition/uncoarsening/refinement/kway_graph_refinement/multitry_kway_fm.cpp - lib/algorithms/cycle_search.cpp - lib/partition/uncoarsening/refinement/cycle_improvements/cycle_refinement.cpp - lib/parallel_mh/galinier_combine/gal_combine.cpp - lib/parallel_mh/galinier_combine/construct_partition.cpp - lib/partition/uncoarsening/refinement/tabu_search/tabu_search.cpp) -add_library(libmodified_kaffpa OBJECT ${LIBMODIFIED_KAFFPA_SOURCE_FILES}) + lib/data_structure/graph_hierarchy.cpp + lib/algorithms/strongly_connected_components.cpp + lib/algorithms/topological_sort.cpp + lib/io/graph_io.cpp + lib/tools/quality_metrics.cpp + lib/tools/graph_extractor.cpp + lib/tools/misc.cpp + lib/tools/partition_snapshooter.cpp + lib/partition/graph_partitioner.cpp + lib/partition/w_cycles/wcycle_partitioner.cpp + lib/partition/coarsening/coarsening.cpp + lib/partition/coarsening/contraction.cpp + lib/partition/coarsening/edge_rating/edge_ratings.cpp + lib/partition/coarsening/matching/matching.cpp + lib/partition/coarsening/matching/random_matching.cpp + lib/partition/coarsening/matching/gpa/path.cpp + lib/partition/coarsening/matching/gpa/gpa_matching.cpp + lib/partition/coarsening/matching/gpa/path_set.cpp + lib/partition/coarsening/clustering/node_ordering.cpp + lib/partition/coarsening/clustering/size_constraint_label_propagation.cpp + lib/partition/initial_partitioning/initial_partitioning.cpp + lib/partition/initial_partitioning/initial_partitioner.cpp + lib/partition/initial_partitioning/initial_partition_bipartition.cpp + lib/partition/initial_partitioning/initial_refinement/initial_refinement.cpp + lib/partition/initial_partitioning/bipartition.cpp + lib/partition/uncoarsening/uncoarsening.cpp + lib/partition/uncoarsening/separator/vertex_separator_algorithm.cpp + lib/partition/uncoarsening/separator/vertex_separator_flow_solver.cpp + lib/partition/uncoarsening/refinement/cycle_improvements/greedy_neg_cycle.cpp + lib/partition/uncoarsening/refinement/cycle_improvements/problem_factory.cpp + lib/partition/uncoarsening/refinement/cycle_improvements/augmented_Qgraph.cpp + lib/partition/uncoarsening/refinement/mixed_refinement.cpp + lib/partition/uncoarsening/refinement/label_propagation_refinement/label_propagation_refinement.cpp + lib/partition/uncoarsening/refinement/refinement.cpp + lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/two_way_fm.cpp + lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/two_way_flow_refinement.cpp + lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/boundary_bfs.cpp + lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/flow_solver.cpp + lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/edge_cut_flow_solver.cpp + lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/timer.cpp + lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/most_balanced_minimum_cuts/most_balanced_minimum_cuts.cpp + lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_refinement.cpp + lib/partition/uncoarsening/refinement/quotient_graph_refinement/complete_boundary.cpp + lib/partition/uncoarsening/refinement/quotient_graph_refinement/partial_boundary.cpp + lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/quotient_graph_scheduling.cpp + lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/simple_quotient_graph_scheduler.cpp + lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/active_block_quotient_graph_scheduler.cpp + lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement.cpp + lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_core.cpp + lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_commons.cpp + lib/partition/uncoarsening/refinement/cycle_improvements/augmented_Qgraph_fabric.cpp + lib/partition/uncoarsening/refinement/cycle_improvements/advanced_models.cpp + lib/partition/uncoarsening/refinement/kway_graph_refinement/multitry_kway_fm.cpp + lib/algorithms/cycle_search.cpp + lib/partition/uncoarsening/refinement/cycle_improvements/cycle_refinement.cpp + lib/partition/uncoarsening/refinement/tabu_search/tabu_search.cpp +) +add_library( + modified_kahip_core_obj + OBJECT + ${LIBMODIFIED_KAFFPA_SOURCE_FILES} +) +kahip_configure_modified_object(modified_kahip_core_obj) +kahip_add_private_header_set( + modified_kahip_core_obj + "${CMAKE_CURRENT_SOURCE_DIR}" + ${MODIFIED_KAHIP_CORE_HEADERS} +) +target_sources( + modified_kahip_core_obj + PRIVATE + FILE_SET shared_bipartition_headers + TYPE HEADERS + BASE_DIRS "${PROJECT_SOURCE_DIR}/lib" + FILES + "${PROJECT_SOURCE_DIR}/lib/partition/initial_partitioning/bipartition_candidate.h" +) set(LIBMODIFIED_KAFFPA_PARALLEL_SOURCE_FILES - lib/parallel_mh/parallel_mh_async.cpp - lib/parallel_mh/population.cpp - lib/parallel_mh/exchange/exchanger.cpp - lib/tools/graph_communication.cpp - lib/tools/mpi_tools.cpp) -add_library(libmodified_kaffpa_async OBJECT ${LIBMODIFIED_KAFFPA_PARALLEL_SOURCE_FILES}) + lib/parallel_mh/parallel_mh_async.cpp + lib/parallel_mh/population.cpp + lib/parallel_mh/galinier_combine/gal_combine.cpp + lib/parallel_mh/galinier_combine/construct_partition.cpp + lib/parallel_mh/exchange/exchanger.cpp +) +add_library( + modified_kahip_collective_obj + OBJECT + ${LIBMODIFIED_KAFFPA_PARALLEL_SOURCE_FILES} +) +kahip_configure_modified_object(modified_kahip_collective_obj) +kahip_add_private_header_set( + modified_kahip_collective_obj + "${CMAKE_CURRENT_SOURCE_DIR}" + ${MODIFIED_KAHIP_COLLECTIVE_HEADERS} +) +target_sources( + modified_kahip_collective_obj + PRIVATE + FILE_SET shared_parallel_mh_headers + TYPE HEADERS + BASE_DIRS "${PROJECT_SOURCE_DIR}/lib" + FILES + "${PROJECT_SOURCE_DIR}/lib/parallel_mh/evolutionary_collectives.h" + "${PROJECT_SOURCE_DIR}/lib/parallel_mh/evolutionary_feasibility.h" + "${PROJECT_SOURCE_DIR}/lib/parallel_mh/population_size_broadcast.h" +) +target_link_libraries( + modified_kahip_collective_obj + PRIVATE kahip_fatal_diagnostics +) + +add_library( + modified_kahip_evolutionary_interface_obj + OBJECT + interface/kaHIP_evolutionary_interface.cpp +) +kahip_configure_modified_object(modified_kahip_evolutionary_interface_obj) +kahip_add_private_header_set( + modified_kahip_evolutionary_interface_obj + "${CMAKE_CURRENT_SOURCE_DIR}" + "${CMAKE_CURRENT_SOURCE_DIR}/interface/kaHIP_interface.h" + "${CMAKE_CURRENT_SOURCE_DIR}/interface/kaHIP_evolutionary_interface_internal.h" + "${CMAKE_CURRENT_SOURCE_DIR}/interface/kaHIP_interface_internal.h" +) +target_link_libraries( + modified_kahip_evolutionary_interface_obj + PRIVATE kahip_fatal_diagnostics +) -add_library(libmodified_kahip_interface STATIC interface/kaHIP_interface.cpp $ $) +add_library(libmodified_kahip_interface STATIC interface/kaHIP_interface.cpp) +kahip_add_private_header_set( + libmodified_kahip_interface + "${CMAKE_CURRENT_SOURCE_DIR}" + "${CMAKE_CURRENT_SOURCE_DIR}/interface/kaHIP_interface.h" +) +target_link_libraries( + libmodified_kahip_interface + PRIVATE + modified_kahip_core_obj + modified_kahip_collective_obj + modified_kahip_evolutionary_interface_obj +) target_link_libraries(libmodified_kahip_interface PRIVATE OpenMP::OpenMP_CXX) -target_include_directories(libmodified_kahip_interface PUBLIC interface) +target_link_libraries(libmodified_kahip_interface PRIVATE MPI::MPI_CXX) +target_link_libraries(libmodified_kahip_interface PRIVATE kahip_options kahip_warnings) +kahip_add_header_root_file_sets( + libmodified_kahip_interface + modified_interface_header_root + "${CMAKE_CURRENT_SOURCE_DIR}/interface" +) diff --git a/parallel/modified_kahip/app/configuration.h b/parallel/modified_kahip/app/configuration.h index 152e3da9..c897d373 100644 --- a/parallel/modified_kahip/app/configuration.h +++ b/parallel/modified_kahip/app/configuration.h @@ -9,24 +9,26 @@ #define CONFIGURATION_3APG5V7Z #include "partition/partition_config.h" - +namespace kahip::modified { class configuration { - public: - configuration() {} ; - virtual ~configuration() {}; - - void strong( PartitionConfig & config ); - void eco( PartitionConfig & config ); - void fast( PartitionConfig & config ); - void standard( PartitionConfig & config ); - void standardsnw( PartitionConfig & config ); - - void fastsocial( PartitionConfig & config ); - void ecosocial( PartitionConfig & config ); - void strongsocial( PartitionConfig & config ); +public: + configuration() {} ; + virtual ~configuration() {}; + + void strong(kahip::modified::PartitionConfig & config ); + void eco(kahip::modified::PartitionConfig & config ); + void fast(kahip::modified::PartitionConfig & config ); + void standard(kahip::modified::PartitionConfig & config ); + void standardsnw(kahip::modified::PartitionConfig & config ); + + void fastsocial(kahip::modified::PartitionConfig & config ); + void ecosocial(kahip::modified::PartitionConfig & config ); + void strongsocial(kahip::modified::PartitionConfig & config ); }; -inline void configuration::strong( PartitionConfig & partition_config ) { +inline void configuration::strong( + kahip::modified::PartitionConfig & partition_config ) { + using namespace kahip::modified; standard(partition_config); partition_config.matching_type = MATCHING_GPA; partition_config.permutation_quality = PERMUTATION_QUALITY_GOOD; @@ -44,7 +46,7 @@ inline void configuration::strong( PartitionConfig & partition_config ) { partition_config.kway_adaptive_limits_alpha = 10; partition_config.kway_rounds = 10; partition_config.rate_first_level_inner_outer = true; - partition_config.use_wcycles = false; + partition_config.use_wcycles = false; partition_config.no_new_initial_partitioning = true; partition_config.use_fullmultigrid = true; partition_config.most_balanced_minimum_cuts = true; @@ -52,8 +54,8 @@ inline void configuration::strong( PartitionConfig & partition_config ) { partition_config.local_multitry_rounds = 10; partition_config.mh_initial_population_fraction = 10; - partition_config.mh_flip_coin = 1; - partition_config.epsilon = 3; + partition_config.mh_flip_coin = 1; + partition_config.epsilon = 3; partition_config.initial_partitioning_type = INITIAL_PARTITIONING_RECPARTITION; partition_config.bipartition_tries = 4; @@ -63,11 +65,14 @@ inline void configuration::strong( PartitionConfig & partition_config ) { } -inline void configuration::eco( PartitionConfig & partition_config ) { +inline void configuration::eco( + kahip::modified::PartitionConfig & partition_config ) { + using namespace kahip::modified; + standard(partition_config); partition_config.eco = true; partition_config.aggressive_random_levels = std::max(2, (int)(7 - log2(partition_config.k))); - + partition_config.kway_rounds = std::min(5, (int)log2(partition_config.k)); partition_config.matching_type = MATCHING_RANDOM_GPA; partition_config.permutation_quality = PERMUTATION_QUALITY_NONE; @@ -80,7 +85,7 @@ inline void configuration::eco( PartitionConfig & partition_config ) { partition_config.kway_stop_rule = KWAY_SIMPLE_STOP_RULE; partition_config.kway_fm_search_limit = 1; partition_config.mh_initial_population_fraction = 50; - partition_config.mh_flip_coin = 1; + partition_config.mh_flip_coin = 1; partition_config.initial_partitioning_type = INITIAL_PARTITIONING_RECPARTITION; partition_config.bipartition_tries = 4; @@ -88,17 +93,19 @@ inline void configuration::eco( PartitionConfig & partition_config ) { partition_config.initial_partitioning_repetitions = 16; } -inline void configuration::fast( PartitionConfig & partition_config ) { +inline void configuration::fast( + kahip::modified::PartitionConfig & partition_config ) { standard(partition_config); + using namespace kahip::modified; partition_config.fast = true; if(partition_config.k > 8) { partition_config.quotient_graph_refinement_disabled = true; - partition_config.kway_fm_search_limit = 0; - partition_config.kway_stop_rule = KWAY_SIMPLE_STOP_RULE; - partition_config.corner_refinement_enabled = true; + partition_config.kway_fm_search_limit = 0; + partition_config.kway_stop_rule = KWAY_SIMPLE_STOP_RULE; + partition_config.corner_refinement_enabled = true; } else { - partition_config.corner_refinement_enabled = false; + partition_config.corner_refinement_enabled = false; } partition_config.permutation_quality = PERMUTATION_QUALITY_FAST; partition_config.permutation_during_refinement = PERMUTATION_QUALITY_NONE; @@ -116,7 +123,9 @@ inline void configuration::fast( PartitionConfig & partition_config ) { } -inline void configuration::standard( PartitionConfig & partition_config ) { +inline void configuration::standard( + kahip::modified::PartitionConfig & partition_config ) { + using namespace kahip::modified; partition_config.seed = 0; partition_config.fast = false; partition_config.eco = false; @@ -130,15 +139,16 @@ inline void configuration::standard( PartitionConfig & partition_config ) { partition_config.permutation_quality = PERMUTATION_QUALITY_FAST; partition_config.graph_allready_partitioned = false; partition_config.initial_partitioning = false; + partition_config.initial_partitioning_type = INITIAL_PARTITIONING_RECPARTITION; partition_config.bipartition_tries = 9; partition_config.minipreps = 10; partition_config.enable_omp = false; partition_config.combine = false; - partition_config.epsilon = 3; + partition_config.epsilon = 3; partition_config.buffoon = false; partition_config.ultra_fast_kaffpaE_interfacecall = false; - partition_config.time_limit = 0; + partition_config.time_limit = 0; partition_config.mh_pool_size = 5; partition_config.mh_plain_repetitions = false; partition_config.no_unsuc_reps = 10; @@ -147,7 +157,7 @@ inline void configuration::standard( PartitionConfig & partition_config ) { partition_config.mh_disable_nc_combine = false; partition_config.mh_disable_cross_combine = false; partition_config.mh_disable_combine = false; - partition_config.mh_enable_quickstart = false; + partition_config.mh_enable_quickstart = false; partition_config.mh_disable_diversify_islands = false; partition_config.mh_diversify = true; partition_config.mh_diversify_best = false; @@ -158,8 +168,8 @@ inline void configuration::standard( PartitionConfig & partition_config ) { partition_config.mh_print_log = false; partition_config.mh_penalty_for_unconnected = false; partition_config.mh_no_mh = false; - partition_config.mh_optimize_communication_volume = false; - partition_config.use_bucket_queues = true; + partition_config.mh_optimize_communication_volume = false; + partition_config.use_bucket_queues = true; partition_config.walshaw_mh_repetitions = 50; partition_config.scaleing_factor = 1; partition_config.scale_back = false; @@ -169,7 +179,7 @@ inline void configuration::standard( PartitionConfig & partition_config ) { partition_config.suppress_partitioner_output = false; - if( partition_config.k <= 4 ) { + if( partition_config.k <= 4 ) { partition_config.bipartition_post_fm_limits = 30; partition_config.bipartition_post_ml_limits = 6; } else { @@ -240,13 +250,13 @@ inline void configuration::standard( PartitionConfig & partition_config ) { partition_config.maxIter = 500000; if( partition_config.k <= 8 ) { - partition_config.kaba_internal_no_aug_steps_aug = 15; + partition_config.kaba_internal_no_aug_steps_aug = 15; } else { - partition_config.kaba_internal_no_aug_steps_aug = 7; + partition_config.kaba_internal_no_aug_steps_aug = 7; } partition_config.kaba_unsucc_iterations = 6; - partition_config.initial_bipartitioning = false; + partition_config.initial_bipartitioning = false; partition_config.kabapE = false; @@ -275,7 +285,9 @@ inline void configuration::standard( PartitionConfig & partition_config ) { } -inline void configuration::standardsnw( PartitionConfig & partition_config ) { +inline void configuration::standardsnw( + kahip::modified::PartitionConfig & partition_config ) { + using namespace kahip::modified; partition_config.matching_type = CLUSTER_COARSENING; partition_config.stop_rule = STOP_RULE_MULTIPLE_K; partition_config.num_vert_stop_factor = 5000; @@ -301,7 +313,8 @@ inline void configuration::standardsnw( PartitionConfig & partition_config ) { } -inline void configuration::fastsocial( PartitionConfig & partition_config ) { +inline void configuration::fastsocial( + kahip::modified::PartitionConfig & partition_config ) { eco(partition_config); standardsnw(partition_config); partition_config.label_propagation_refinement = true; @@ -309,7 +322,8 @@ inline void configuration::fastsocial( PartitionConfig & partition_config ) { partition_config.balance_factor = 0; } -inline void configuration::ecosocial( PartitionConfig & partition_config ) { +inline void configuration::ecosocial( + kahip::modified::PartitionConfig & partition_config ) { eco(partition_config); standardsnw(partition_config); partition_config.label_propagation_refinement = false; @@ -320,7 +334,8 @@ inline void configuration::ecosocial( PartitionConfig & partition_config ) { partition_config.cluster_coarsening_during_ip = true; } -inline void configuration::strongsocial( PartitionConfig & partition_config ) { +inline void configuration::strongsocial( + kahip::modified::PartitionConfig & partition_config ) { strong(partition_config); standardsnw(partition_config); @@ -330,5 +345,5 @@ inline void configuration::strongsocial( PartitionConfig & partition_config ) { } - +} #endif /* end of include guard: CONFIGURATION_3APG5V7Z */ diff --git a/parallel/modified_kahip/app/interface_test.cpp b/parallel/modified_kahip/app/interface_test.cpp index 28d1e0c1..0b1df349 100644 --- a/parallel/modified_kahip/app/interface_test.cpp +++ b/parallel/modified_kahip/app/interface_test.cpp @@ -30,7 +30,7 @@ int main(int argn, char **argv) { - + using namespace kahip::modified; PartitionConfig partition_config; std::string graph_filename; diff --git a/parallel/modified_kahip/app/parse_parameters.h b/parallel/modified_kahip/app/parse_parameters.h index 4323e44c..89416392 100644 --- a/parallel/modified_kahip/app/parse_parameters.h +++ b/parallel/modified_kahip/app/parse_parameters.h @@ -12,7 +12,7 @@ #include #endif #include "configuration.h" - +namespace kahip::modified { int parse_parameters(int argn, char **argv, PartitionConfig & partition_config, std::string & graph_filename, @@ -754,5 +754,5 @@ int parse_parameters(int argn, char **argv, return 0; } - +} #endif /* end of include guard: PARSE_PARAMETERS_GPJMGSM8 */ diff --git a/parallel/modified_kahip/interface/kaHIP_evolutionary_interface.cpp b/parallel/modified_kahip/interface/kaHIP_evolutionary_interface.cpp new file mode 100644 index 00000000..cacc4e07 --- /dev/null +++ b/parallel/modified_kahip/interface/kaHIP_evolutionary_interface.cpp @@ -0,0 +1,286 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../app/configuration.h" +#include "../lib/parallel_mh/parallel_mh_async.h" +#include "parallel_mh/evolutionary_collectives.h" +#include "../lib/tools/quality_metrics.h" +#include "../../shared/imbalance.h" +#include "../../shared/random_state.h" +#include "kaHIP_interface.h" +#include "kaHIP_interface_internal.h" +#include "tools/fatal_diagnostics.h" + +namespace { +struct public_balance final { + unsigned imbalance_percent; + std::uint64_t upper_bound; +}; + +[[nodiscard]] auto exact_public_balance(int const* n, + int const* vertex_weights, + int const* block_count, + double const* imbalance) + -> public_balance { + if (n == nullptr || block_count == nullptr || imbalance == nullptr) { + throw std::invalid_argument( + "modified kaffpaE requires non-null size, block-count, and imbalance " + "arguments"); + } + if (*n < 0 || *block_count <= 0) { + throw std::invalid_argument( + "modified kaffpaE requires a non-negative vertex count and a " + "positive block count"); + } + + auto const normalized_imbalance = + kahip::balance::normalize_fractional_imbalance(*imbalance); + if (!normalized_imbalance.has_value()) { + throw std::overflow_error( + "modified kaffpaE imbalance percentage exceeds the unsigned int " + "domain"); + } + + auto total_weight = std::uint64_t{0}; + for (auto node = 0; node < *n; ++node) { + auto const weight = vertex_weights == nullptr ? 1 : vertex_weights[node]; + if (weight < 0) { + throw std::invalid_argument( + "modified kaffpaE requires non-negative vertex weights"); + } + if (!kahip::random_compat::checked_add( + total_weight, static_cast(weight))) { + throw std::overflow_error( + "modified kaffpaE total vertex weight exceeds the uint64 domain"); + } + } + + auto const upper_bound = kahip::random_compat::exact_partition_upper_bound( + total_weight, static_cast(*block_count), + normalized_imbalance->effective_percent); + if (!upper_bound.has_value()) { + throw std::overflow_error( + "modified kaffpaE partition upper bound exceeds the uint64 domain"); + } + return public_balance{.imbalance_percent = normalized_imbalance->effective_percent, + .upper_bound = *upper_bound}; +} + +[[noreturn]] void abort_kaffpae_boundary(MPI_Comm communicator, + std::exception_ptr failure) noexcept { + auto active = false; + auto rank = std::optional{}; + auto lifecycle_error = std::optional{}; + auto initialized = 0; + auto const initialized_result = MPI_Initialized(&initialized); + if (initialized_result != MPI_SUCCESS) { + lifecycle_error = initialized_result; + } else if (initialized != 0) { + auto finalized = 0; + auto const finalized_result = MPI_Finalized(&finalized); + if (finalized_result != MPI_SUCCESS) { + lifecycle_error = finalized_result; + } else { + active = finalized == 0; + } + } + + auto const affected = + communicator == MPI_COMM_NULL ? MPI_COMM_WORLD : communicator; + if (active) { + auto local_rank = 0; + if (MPI_Comm_rank(affected, &local_rank) == MPI_SUCCESS) { + rank = local_rank; + } + } + + if (lifecycle_error.has_value()) { + kahip::diagnostics::critical( + "modified kaffpaE C boundary could not query MPI lifecycle " + "(raw code ", + *lifecycle_error, ")"); + } + try { + std::rethrow_exception(failure); + } catch (std::exception const& error) { + if (rank.has_value()) { + kahip::diagnostics::critical( + "modified kaffpaE C boundary: ", error.what(), " (rank ", *rank, + ")"); + } else { + kahip::diagnostics::critical("modified kaffpaE C boundary: ", + error.what()); + } + } catch (...) { + if (rank.has_value()) { + kahip::diagnostics::critical( + "modified kaffpaE C boundary: unknown unrecoverable exception " + "(rank ", + *rank, ")"); + } else { + kahip::diagnostics::critical( + "modified kaffpaE C boundary: unknown unrecoverable exception"); + } + } + if (active) { + static_cast(MPI_Abort(affected, EXIT_FAILURE)); + } + std::abort(); +} + +void kaffpae_impl(int* n, + int* vwgt, + int* xadj, + int* adjcwgt, + int* adjncy, + int* nparts, + bool suppress_output, + bool graph_partitioned, + int time_limit, + int seed, + int mode, + MPI_Comm communicator, + unsigned authoritative_imbalance_percent, + int* edgecut, + double* balance, + int* part, + std::optional + authoritative_upper_bound) { + using namespace kahip::modified; + configuration cfg; + PartitionConfig partition_config; + partition_config.k = *nparts; + cfg.standard(partition_config); + + switch (mode) { + case FAST: + cfg.fast(partition_config); + break; + case ECO: + cfg.eco(partition_config); + break; + case STRONG: + cfg.strong(partition_config); + break; + case FASTSOCIAL: + cfg.fastsocial(partition_config); + break; + case ULTRAFASTSOCIAL: + cfg.fastsocial(partition_config); + partition_config.ultra_fast_kaffpaE_interfacecall = true; + break; + case ECOSOCIAL: + cfg.ecosocial(partition_config); + break; + case STRONGSOCIAL: + cfg.strongsocial(partition_config); + break; + default: + cfg.eco(partition_config); + break; + } + + partition_config.seed = seed; + partition_config.k = *nparts; + partition_config.imbalance = authoritative_imbalance_percent; + partition_config.time_limit = time_limit; + partition_config.kabapE = false; + + graph_access graph; + internal_build_graph(partition_config, n, vwgt, xadj, adjcwgt, adjncy, graph, + authoritative_upper_bound); + + partition_config.kway_adaptive_limits_beta = + std::log(partition_config.largest_graph_weight); + + if (graph_partitioned) { + forall_nodes(graph, node) { + graph.setPartitionIndex(node, part[node]); + } + endfor + } + + partition_config.graph_allready_partitioned = graph_partitioned; + partition_config.no_new_initial_partitioning = graph_partitioned; + + parallel_mh_async multilevel(communicator); + multilevel.perform_partitioning(partition_config, graph); + + forall_nodes(graph, node) { + part[node] = graph.getPartitionIndex(node); + } + endfor + + quality_metrics metrics; + *edgecut = metrics.edge_cut(graph); + *balance = metrics.balance(graph); + + static_cast(suppress_output); +} +} // namespace + +void kahip::modified::kaffpaE_with_upper_bound( + int* n, + int* vwgt, + int* xadj, + int* adjcwgt, + int* adjncy, + int* nparts, + bool suppress_output, + bool graph_partitioned, + int time_limit, + int seed, + int mode, + MPI_Comm communicator, + unsigned authoritative_imbalance_percent, + std::uint64_t authoritative_upper_bound, + int* edgecut, + double* balance, + int* part) { + auto const narrowed = + kahip::random_compat::checked_narrow( + authoritative_upper_bound); + if (!narrowed.has_value()) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + communicator, "evolutionary partition upper bound", + "ParHIP upper bound exceeds the modified KaHIP weight domain"); + } + kaffpae_impl(n, vwgt, xadj, adjcwgt, adjncy, nparts, + suppress_output, graph_partitioned, time_limit, seed, mode, + communicator, authoritative_imbalance_percent, edgecut, balance, + part, *narrowed); +} + +void kaffpaE(int* n, + int* vwgt, + int* xadj, + int* adjcwgt, + int* adjncy, + int* nparts, + double* imbalance, + bool suppress_output, + bool graph_partitioned, + int time_limit, + int seed, + int mode, + MPI_Comm communicator, + int* edgecut, + double* balance, + int* part) noexcept { + try { + auto const public_result = exact_public_balance(n, vwgt, nparts, imbalance); + kahip::modified::kaffpaE_with_upper_bound( + n, vwgt, xadj, adjcwgt, adjncy, nparts, suppress_output, + graph_partitioned, time_limit, seed, mode, communicator, + public_result.imbalance_percent, public_result.upper_bound, edgecut, + balance, part); + } catch (...) { + abort_kaffpae_boundary(communicator, std::current_exception()); + } +} diff --git a/parallel/modified_kahip/interface/kaHIP_evolutionary_interface_internal.h b/parallel/modified_kahip/interface/kaHIP_evolutionary_interface_internal.h new file mode 100644 index 00000000..eb74bf2d --- /dev/null +++ b/parallel/modified_kahip/interface/kaHIP_evolutionary_interface_internal.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include + +namespace kahip::modified { +void kaffpaE_with_upper_bound(int* n, + int* vwgt, + int* xadj, + int* adjcwgt, + int* adjncy, + int* nparts, + bool suppress_output, + bool graph_partitioned, + int time_limit, + int seed, + int mode, + MPI_Comm communicator, + unsigned authoritative_imbalance_percent, + std::uint64_t authoritative_upper_bound, + int* edgecut, + double* balance, + int* part); +} // namespace kahip::modified diff --git a/parallel/modified_kahip/interface/kaHIP_interface.cpp b/parallel/modified_kahip/interface/kaHIP_interface.cpp index ada8f038..e9af8b03 100644 --- a/parallel/modified_kahip/interface/kaHIP_interface.cpp +++ b/parallel/modified_kahip/interface/kaHIP_interface.cpp @@ -14,315 +14,193 @@ #include "../lib/tools/quality_metrics.h" #include "../lib/tools/macros_assertions.h" #include "../lib/tools/random_functions.h" -#include "../lib/parallel_mh/parallel_mh_async.h" #include "../lib/partition/partition_config.h" #include "../lib/partition/graph_partitioner.h" #include "../lib/partition/uncoarsening/separator/vertex_separator_algorithm.h" #include "../app/configuration.h" +#include "kaHIP_interface_internal.h" using namespace std; - -void internal_build_graph( PartitionConfig & partition_config, - int* n, - int* vwgt, - int* xadj, - int* adjcwgt, - int* adjncy, - graph_access & G) { - G.build_from_metis(*n, xadj, adjncy); - G.set_partition_count(partition_config.k); - - srand(partition_config.seed); - random_functions::setSeed(partition_config.seed); - - if(vwgt != NULL) { - forall_nodes(G, node) { - G.setNodeWeight(node, vwgt[node]); - } endfor - } - - if(adjcwgt != NULL) { - forall_edges(G, e) { - G.setEdgeWeight(e, adjcwgt[e]); - } endfor - } - - partition_config.largest_graph_weight = 0; - forall_nodes(G, node) { - partition_config.largest_graph_weight += G.getNodeWeight(node); - } endfor - - double epsilon = partition_config.imbalance/100; - partition_config.upper_bound_partition = ceil((1+epsilon)*partition_config.largest_graph_weight/(double)partition_config.k); - partition_config.graph_allready_partitioned = false; - -} - - -void internal_kaffpa_call(PartitionConfig & partition_config, - bool suppress_output, - int* n, - int* vwgt, - int* xadj, - int* adjcwgt, - int* adjncy, - int* nparts, - double* imbalance, - int* edgecut, +namespace kahip::modified { +void internal_kaffpa_call(PartitionConfig & partition_config, + bool suppress_output, + int* n, + int* vwgt, + int* xadj, + int* adjcwgt, + int* adjncy, + int* nparts, + double* imbalance, + int* edgecut, int* part) { - streambuf* backup = cout.rdbuf(); - ofstream ofs; - ofs.open("/dev/null"); - if(suppress_output) { - cout.rdbuf(ofs.rdbuf()); - } + streambuf* backup = cout.rdbuf(); + ofstream ofs; + ofs.open("/dev/null"); + if(suppress_output) { + cout.rdbuf(ofs.rdbuf()); + } - partition_config.imbalance = 100*(*imbalance); - graph_access G; - internal_build_graph( partition_config, n, vwgt, xadj, adjcwgt, adjncy, G); + partition_config.imbalance = 100*(*imbalance); + graph_access G; + internal_build_graph( partition_config, n, vwgt, xadj, adjcwgt, adjncy, G); - - timer t; - graph_partitioner partitioner; - partitioner.perform_partitioning(partition_config, G); - std::cout << "partioning took " << t.elapsed() << std::endl; - forall_nodes(G, node) { - part[node] = G.getPartitionIndex(node); - } endfor + timer t; + graph_partitioner partitioner; + partitioner.perform_partitioning(partition_config, G); + std::cout << "partioning took " << t.elapsed() << std::endl; - quality_metrics qm; - *edgecut = qm.edge_cut(G); + forall_nodes(G, node) { + part[node] = G.getPartitionIndex(node); + } endfor - ofs.close(); - cout.rdbuf(backup); -} + quality_metrics qm; + *edgecut = qm.edge_cut(G); + ofs.close(); + cout.rdbuf(backup); +} +} -void kaffpa(int* n, - int* vwgt, - int* xadj, - int* adjcwgt, - int* adjncy, - int* nparts, - double* imbalance, - bool suppress_output, +void kaffpa(int* n, + int* vwgt, + int* xadj, + int* adjcwgt, + int* adjncy, + int* nparts, + double* imbalance, + bool suppress_output, int seed, int mode, - int* edgecut, + int* edgecut, int* part) { - configuration cfg; - PartitionConfig partition_config; - partition_config.k = *nparts; - - switch( mode ) { - case FAST: - cfg.fast(partition_config); - break; - case ECO: - cfg.eco(partition_config); - break; - case STRONG: - cfg.strong(partition_config); - break; - case FASTSOCIAL: - cfg.fastsocial(partition_config); - break; - case ECOSOCIAL: - cfg.ecosocial(partition_config); - break; - case STRONGSOCIAL: - cfg.strongsocial(partition_config); - break; - default: - - cfg.eco(partition_config); - break; - } - - partition_config.seed = seed; - internal_kaffpa_call(partition_config, suppress_output, n, vwgt, xadj, adjcwgt, adjncy, nparts, imbalance, edgecut, part); + using namespace kahip::modified; + configuration cfg; + PartitionConfig partition_config; + partition_config.k = *nparts; + + switch( mode ) { + case FAST: + cfg.fast(partition_config); + break; + case ECO: + cfg.eco(partition_config); + break; + case STRONG: + cfg.strong(partition_config); + break; + case FASTSOCIAL: + cfg.fastsocial(partition_config); + break; + case ECOSOCIAL: + cfg.ecosocial(partition_config); + break; + case STRONGSOCIAL: + cfg.strongsocial(partition_config); + break; + default: + + cfg.eco(partition_config); + break; + } + + partition_config.seed = seed; + internal_kaffpa_call(partition_config, suppress_output, n, vwgt, xadj, adjcwgt, adjncy, nparts, imbalance, edgecut, part); } - -void internal_nodeseparator_call(PartitionConfig & partition_config, - bool suppress_output, - int* n, - int* vwgt, - int* xadj, - int* adjcwgt, - int* adjncy, - int* nparts, - double* imbalance, - int* num_nodeseparator_vertices, +namespace kahip::modified { +void internal_nodeseparator_call(PartitionConfig & partition_config, + bool suppress_output, + int* n, + int* vwgt, + int* xadj, + int* adjcwgt, + int* adjncy, + int* nparts, + double* imbalance, + int* num_nodeseparator_vertices, int** separator) { - //first perform std partitioning using KaFFPa - streambuf* backup = cout.rdbuf(); - ofstream ofs; - ofs.open("/dev/null"); - if(suppress_output) { - cout.rdbuf(ofs.rdbuf()); - } + //first perform std partitioning using KaFFPa + streambuf* backup = cout.rdbuf(); + ofstream ofs; + ofs.open("/dev/null"); + if(suppress_output) { + cout.rdbuf(ofs.rdbuf()); + } - partition_config.imbalance = 100*(*imbalance); - graph_access G; - internal_build_graph( partition_config, n, vwgt, xadj, adjcwgt, adjncy, G); + partition_config.imbalance = 100*(*imbalance); + graph_access G; + internal_build_graph( partition_config, n, vwgt, xadj, adjcwgt, adjncy, G); - - graph_partitioner partitioner; - partitioner.perform_partitioning(partition_config, G); - // now compute a node separator from the partition of the graph - complete_boundary boundary(&G); - boundary.build(); + graph_partitioner partitioner; + partitioner.perform_partitioning(partition_config, G); - vertex_separator_algorithm vsa; - std::vector internal_separator; - vsa.compute_vertex_separator(partition_config, G, boundary, internal_separator); + // now compute a node separator from the partition of the graph + complete_boundary boundary(&G); + boundary.build(); - // copy to output variables - *num_nodeseparator_vertices = internal_separator.size(); - *separator = new int[*num_nodeseparator_vertices]; - for( unsigned int i = 0; i < internal_separator.size(); i++) { - (*separator)[i] = internal_separator[i]; - } + vertex_separator_algorithm vsa; + std::vector internal_separator; + vsa.compute_vertex_separator(partition_config, G, boundary, internal_separator); - ofs.close(); - cout.rdbuf(backup); -} + // copy to output variables + *num_nodeseparator_vertices = internal_separator.size(); + *separator = new int[*num_nodeseparator_vertices]; + for( unsigned int i = 0; i < internal_separator.size(); i++) { + (*separator)[i] = internal_separator[i]; + } + ofs.close(); + cout.rdbuf(backup); +} +} -void node_separator(int* n, - int* vwgt, - int* xadj, - int* adjcwgt, - int* adjncy, - int* nparts, - double* imbalance, - bool suppress_output, +void node_separator(int* n, + int* vwgt, + int* xadj, + int* adjcwgt, + int* adjncy, + int* nparts, + double* imbalance, + bool suppress_output, int seed, int mode, - int* num_separator_vertices, + int* num_separator_vertices, int** separator) { - configuration cfg; - PartitionConfig partition_config; - partition_config.k = *nparts; - - switch( mode ) { - case FAST: - cfg.fast(partition_config); - break; - case ECO: - cfg.eco(partition_config); - break; - case STRONG: - cfg.strong(partition_config); - break; - case FASTSOCIAL: - cfg.fastsocial(partition_config); - break; - case ECOSOCIAL: - cfg.ecosocial(partition_config); - break; - case STRONGSOCIAL: - cfg.strongsocial(partition_config); - break; - default: - cfg.eco(partition_config); - break; - } - partition_config.seed = seed; - - internal_nodeseparator_call(partition_config, suppress_output, n, vwgt, xadj, adjcwgt, adjncy, nparts, imbalance, num_separator_vertices, separator); + using namespace kahip::modified; + configuration cfg; + PartitionConfig partition_config; + partition_config.k = *nparts; + + switch( mode ) { + case FAST: + cfg.fast(partition_config); + break; + case ECO: + cfg.eco(partition_config); + break; + case STRONG: + cfg.strong(partition_config); + break; + case FASTSOCIAL: + cfg.fastsocial(partition_config); + break; + case ECOSOCIAL: + cfg.ecosocial(partition_config); + break; + case STRONGSOCIAL: + cfg.strongsocial(partition_config); + break; + default: + cfg.eco(partition_config); + break; + } + partition_config.seed = seed; + + internal_nodeseparator_call(partition_config, suppress_output, n, vwgt, xadj, adjcwgt, adjncy, nparts, imbalance, num_separator_vertices, separator); } - -void kaffpaE(int* n, - int* vwgt, - int* xadj, - int* adjcwgt, - int* adjncy, - int* nparts, - double* imbalance, - bool suppress_output, - bool graph_partitioned, - int time_limit, - int seed, - int mode, // 0 == strong, 1 == eco, 2 == fast - MPI_Comm communicator, - int* edgecut, - double* balance, - int* part) { - - configuration cfg; - PartitionConfig partition_config; - partition_config.k = *nparts; - cfg.standard(partition_config); - - switch( mode ) { - case FAST: - cfg.fast(partition_config); - break; - case ECO: - cfg.eco(partition_config); - break; - case STRONG: - cfg.strong(partition_config); - break; - case FASTSOCIAL: - cfg.fastsocial(partition_config); - break; - case ULTRAFASTSOCIAL: - cfg.fastsocial(partition_config); - partition_config.ultra_fast_kaffpaE_interfacecall = true; - break; - case ECOSOCIAL: - cfg.ecosocial(partition_config); - break; - case STRONGSOCIAL: - cfg.strongsocial(partition_config); - break; - default: - cfg.eco(partition_config); - break; - } - - partition_config.seed = seed; - partition_config.k = *nparts; - partition_config.imbalance = 100*(*imbalance); - partition_config.time_limit = time_limit; - partition_config.kabapE = false; - - graph_access G; - internal_build_graph( partition_config, n, vwgt, xadj, adjcwgt, adjncy, G); - - partition_config.kway_adaptive_limits_beta = log(partition_config.largest_graph_weight); - - if(graph_partitioned) { - forall_nodes(G, node) { - G.setPartitionIndex(node, part[node]); - } endfor - } - - partition_config.graph_allready_partitioned = graph_partitioned; - partition_config.no_new_initial_partitioning = graph_partitioned; - - parallel_mh_async mh(communicator); - mh.perform_partitioning(partition_config, G); - - forall_nodes(G, node) { - part[node] = G.getPartitionIndex(node); - } endfor - - quality_metrics qm; - *edgecut = qm.edge_cut(G); - *balance = qm.balance(G); - - //ofs.close(); - //cout.rdbuf(backup); -} - - diff --git a/parallel/modified_kahip/interface/kaHIP_interface.h b/parallel/modified_kahip/interface/kaHIP_interface.h index fd063d2a..6cb2eb6f 100644 --- a/parallel/modified_kahip/interface/kaHIP_interface.h +++ b/parallel/modified_kahip/interface/kaHIP_interface.h @@ -12,8 +12,12 @@ #include +#define KAHIP_MODIFIED_NOEXCEPT noexcept + extern "C" { +#else +#define KAHIP_MODIFIED_NOEXCEPT #endif const int FAST = 0; @@ -44,11 +48,14 @@ void kaffpaE(int* n, int* vwgt, int* xadj, int* adjcwgt, bool graph_partitioned, int time_limit, int seed, int mode, MPI_Comm communicator, - int* edgecut, double* balance, int* part); + int* edgecut, double* balance, int* part) + KAHIP_MODIFIED_NOEXCEPT; #ifdef __cplusplus } #endif +#undef KAHIP_MODIFIED_NOEXCEPT + #endif /* end of include guard: KAFFPA_INTERFACE_RYEEZ6WJ */ diff --git a/parallel/modified_kahip/interface/kaHIP_interface_internal.h b/parallel/modified_kahip/interface/kaHIP_interface_internal.h new file mode 100644 index 00000000..af45940d --- /dev/null +++ b/parallel/modified_kahip/interface/kaHIP_interface_internal.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include + +#include "../lib/data_structure/graph_access.h" +#include "../lib/partition/partition_config.h" +#include "../lib/tools/random_functions.h" +#include "kaHIP_evolutionary_interface_internal.h" + +namespace kahip::modified { +inline void internal_build_graph(PartitionConfig& partition_config, + int* n, + int* vwgt, + int* xadj, + int* adjcwgt, + int* adjncy, + graph_access& graph, + std::optional + authoritative_upper_bound = std::nullopt) { + graph.build_from_metis(*n, xadj, adjncy); + graph.set_partition_count(partition_config.k); + + std::srand(partition_config.seed); + random_functions::setSeed(partition_config.seed); + + if (vwgt != nullptr) { + forall_nodes(graph, node) { + graph.setNodeWeight(node, vwgt[node]); + } + endfor + } + + if (adjcwgt != nullptr) { + forall_edges(graph, edge) { + graph.setEdgeWeight(edge, adjcwgt[edge]); + } + endfor + } + + partition_config.largest_graph_weight = 0; + forall_nodes(graph, node) { + partition_config.largest_graph_weight += graph.getNodeWeight(node); + } + endfor + + if (authoritative_upper_bound.has_value()) { + partition_config.upper_bound_partition = *authoritative_upper_bound; + } else { + auto const epsilon = partition_config.imbalance / 100; + partition_config.upper_bound_partition = + std::ceil((1 + epsilon) * partition_config.largest_graph_weight / + static_cast(partition_config.k)); + } + partition_config.graph_allready_partitioned = false; +} + +} // namespace kahip::modified diff --git a/parallel/modified_kahip/lib/algorithms/cycle_search.cpp b/parallel/modified_kahip/lib/algorithms/cycle_search.cpp index 0b74229b..2a73d8f1 100644 --- a/parallel/modified_kahip/lib/algorithms/cycle_search.cpp +++ b/parallel/modified_kahip/lib/algorithms/cycle_search.cpp @@ -11,428 +11,421 @@ #include "cycle_search.h" #include "random_functions.h" #include "timer.h" - +namespace kahip::modified { double cycle_search::total_time = 0; -cycle_search::cycle_search() { - -} - -cycle_search::~cycle_search() { - -} - void cycle_search::find_random_cycle(graph_access & G, std::vector & cycle) { - //first perform a bfs starting from a random node and build the parent array - std::deque* bfsqueue = new std::deque; - NodeID v = random_functions::nextInt(0, G.number_of_nodes()-1); - bfsqueue->push_back(v); - - std::vector touched(G.number_of_nodes(),false); - std::vector is_leaf(G.number_of_nodes(),false); - std::vector parent(G.number_of_nodes(),0); - std::vector leafes; - touched[v] = true; - parent[v] = v; - - while(!bfsqueue->empty()) { - NodeID source = bfsqueue->front(); - bfsqueue->pop_front(); - - bool is_leaf = true; - forall_out_edges(G, e, source) { - NodeID target = G.getEdgeTarget(e); - if(!touched[target]) { - is_leaf = false; - touched[target] = true; - parent[target] = source; - bfsqueue->push_back(target); - } - } endfor - - if(is_leaf) - leafes.push_back(source); - - } - - std::vector sources(G.number_of_edges(), 0); - std::vector targets(G.number_of_edges(), 0); - forall_nodes(G, node) { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - sources[e] = node; - targets[e] = target; - } endfor - } endfor - - - //now find two random leafes - NodeID v_1, v_2; - unsigned r_idx = random_functions::nextInt(0, G.number_of_edges()-1); - while(true) { - NodeID source = sources[r_idx]; - NodeID target = targets[r_idx]; - if( parent[source] != target && parent[target] != source) { - //found a non-tree edge - v_1 = source; - v_2 = target; - break; - } - - r_idx = random_functions::nextInt(0, G.number_of_edges()-1); - } - - // NodeID now climb up the parent array step wise left and right - std::vector lhs_path, rhs_path; - lhs_path.push_back(v_1); - rhs_path.push_back(v_2); - - std::vector touched_nodes(G.number_of_nodes(),false); - std::vector index(G.number_of_nodes(),0); - index[v_1] = 0; - index[v_2] = 0; - touched_nodes[v_1] = true; - touched_nodes[v_2] = true; - - NodeID cur_lhs = v_1, cur_rhs = v_2; - NodeID counter = 0; - bool break_lhs = false; - while(true) { - counter++; - if(cur_lhs != parent[cur_lhs]) { - if(touched_nodes[parent[cur_lhs]] == true) { - break_lhs = true; - lhs_path.push_back(parent[cur_lhs]); - break; - } else { - cur_lhs = parent[cur_lhs]; - touched_nodes[cur_lhs] = true; - lhs_path.push_back(cur_lhs); - index[cur_lhs] = counter; - } - } - if(cur_rhs != parent[cur_rhs]) { - if(touched_nodes[parent[cur_rhs]] == true) { - rhs_path.push_back(parent[cur_rhs]); - break; - } else { - cur_rhs = parent[cur_rhs]; - touched_nodes[cur_rhs] = true; - rhs_path.push_back(cur_rhs); - index[cur_rhs] = counter; - } - } - - } - - if(break_lhs) { - for( unsigned i = 0; i < lhs_path.size(); i++) { - cycle.push_back(lhs_path[i]); - } - - NodeID connecting_vertice = cycle[cycle.size()-1]; - for( int i = index[connecting_vertice]-1; i >= 0; i--) { - cycle.push_back(rhs_path[i]); - } - } else { - for( unsigned i = 0; i < rhs_path.size(); i++) { - cycle.push_back(rhs_path[i]); - } - - NodeID connecting_vertice = cycle[cycle.size()-1]; - for( int i = index[connecting_vertice]-1; i >= 0; i--) { - cycle.push_back(lhs_path[i]); - } - } - - cycle.push_back(cycle[0]); + //first perform a bfs starting from a random node and build the parent array + std::deque* bfsqueue = new std::deque; + NodeID v = random_functions::nextInt(0, G.number_of_nodes()-1); + bfsqueue->push_back(v); + + std::vector touched(G.number_of_nodes(),false); + std::vector is_leaf(G.number_of_nodes(),false); + std::vector parent(G.number_of_nodes(),0); + std::vector leafes; + touched[v] = true; + parent[v] = v; + + while(!bfsqueue->empty()) { + NodeID source = bfsqueue->front(); + bfsqueue->pop_front(); + + bool is_leaf = true; + forall_out_edges(G, e, source) { + NodeID target = G.getEdgeTarget(e); + if(!touched[target]) { + is_leaf = false; + touched[target] = true; + parent[target] = source; + bfsqueue->push_back(target); + } + } endfor + + if(is_leaf) + leafes.push_back(source); + + } + + std::vector sources(G.number_of_edges(), 0); + std::vector targets(G.number_of_edges(), 0); + forall_nodes(G, node) { + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + sources[e] = node; + targets[e] = target; + } endfor + } endfor + + + //now find two random leafes + NodeID v_1, v_2; + unsigned r_idx = random_functions::nextInt(0, G.number_of_edges()-1); + while(true) { + NodeID source = sources[r_idx]; + NodeID target = targets[r_idx]; + if( parent[source] != target && parent[target] != source) { + //found a non-tree edge + v_1 = source; + v_2 = target; + break; + } + + r_idx = random_functions::nextInt(0, G.number_of_edges()-1); + } + + // NodeID now climb up the parent array step wise left and right + std::vector lhs_path, rhs_path; + lhs_path.push_back(v_1); + rhs_path.push_back(v_2); + + std::vector touched_nodes(G.number_of_nodes(),false); + std::vector index(G.number_of_nodes(),0); + index[v_1] = 0; + index[v_2] = 0; + touched_nodes[v_1] = true; + touched_nodes[v_2] = true; + + NodeID cur_lhs = v_1, cur_rhs = v_2; + NodeID counter = 0; + bool break_lhs = false; + while(true) { + counter++; + if(cur_lhs != parent[cur_lhs]) { + if(touched_nodes[parent[cur_lhs]] == true) { + break_lhs = true; + lhs_path.push_back(parent[cur_lhs]); + break; + } else { + cur_lhs = parent[cur_lhs]; + touched_nodes[cur_lhs] = true; + lhs_path.push_back(cur_lhs); + index[cur_lhs] = counter; + } + } + if(cur_rhs != parent[cur_rhs]) { + if(touched_nodes[parent[cur_rhs]] == true) { + rhs_path.push_back(parent[cur_rhs]); + break; + } else { + cur_rhs = parent[cur_rhs]; + touched_nodes[cur_rhs] = true; + rhs_path.push_back(cur_rhs); + index[cur_rhs] = counter; + } + } + + } + + if(break_lhs) { + for( unsigned i = 0; i < lhs_path.size(); i++) { + cycle.push_back(lhs_path[i]); + } + + NodeID connecting_vertice = cycle[cycle.size()-1]; + for( int i = index[connecting_vertice]-1; i >= 0; i--) { + cycle.push_back(rhs_path[i]); + } + } else { + for( unsigned i = 0; i < rhs_path.size(); i++) { + cycle.push_back(rhs_path[i]); + } + + NodeID connecting_vertice = cycle[cycle.size()-1]; + for( int i = index[connecting_vertice]-1; i >= 0; i--) { + cycle.push_back(lhs_path[i]); + } + } + + cycle.push_back(cycle[0]); } -bool cycle_search::find_shortest_path(graph_access & G, - NodeID & start, - NodeID & dest, +bool cycle_search::find_shortest_path(graph_access & G, + NodeID & start, + NodeID & dest, std::vector & cycle) { - std::vector distance(G.number_of_nodes(), std::numeric_limits::max()/2); - std::vector parent(G.number_of_nodes(), std::numeric_limits::max()); - - bool negative_cycle_detected = negative_cycle_detection(G, start, distance, parent, cycle); - - if( !negative_cycle_detected) { - //if there is no negative cycle then we should return a shortest path from s to t - cycle.clear(); - cycle.push_back(dest); - NodeID cur = dest; - while(cur != start) { - cur = parent[cur]; - cycle.push_back(cur); - } - std::reverse(cycle.begin(), cycle.end()); - } - return negative_cycle_detected; + std::vector distance(G.number_of_nodes(), std::numeric_limits::max()/2); + std::vector parent(G.number_of_nodes(), std::numeric_limits::max()); + + bool negative_cycle_detected = negative_cycle_detection(G, start, distance, parent, cycle); + + if( !negative_cycle_detected) { + //if there is no negative cycle then we should return a shortest path from s to t + cycle.clear(); + cycle.push_back(dest); + NodeID cur = dest; + while(cur != start) { + cur = parent[cur]; + cycle.push_back(cur); + } + std::reverse(cycle.begin(), cycle.end()); + } + return negative_cycle_detected; } bool cycle_search::find_negative_cycle(graph_access & G, NodeID & start, std::vector & cycle) { - //simplest bellman ford algorithm + //simplest bellman ford algorithm - std::vector distance(G.number_of_nodes(), std::numeric_limits::max()/2); - std::vector parent(G.number_of_nodes(), std::numeric_limits::max()); + std::vector distance(G.number_of_nodes(), std::numeric_limits::max()/2); + std::vector parent(G.number_of_nodes(), std::numeric_limits::max()); - return negative_cycle_detection(G, start, distance, parent, cycle); + return negative_cycle_detection(G, start, distance, parent, cycle); } -int cycle_search::bellman_ford_with_subtree_disassembly_and_updates(graph_access & G, - NodeID & start, - std::vector & distance, - std::vector & parent, +int cycle_search::bellman_ford_with_subtree_disassembly_and_updates(graph_access & G, + NodeID & start, + std::vector & distance, + std::vector & parent, std::vector & cycle) { - // Goldberg spc-1.2 similar implementation using our data structures - int NULL_NODE = -1; - distance[start] = 0; - std::queue L; - - //doubly linked list of shortest path tree in preorder - short OUT_OF_QUEUE = 0; - short INACTIVE = 1; - short ACTIVE = 2; - short IN_QUEUE = 2; - - std::vector before(G.number_of_nodes(), NULL_NODE); - std::vector after(G.number_of_nodes()); - std::vector degree(G.number_of_nodes()); - std::vector status(G.number_of_nodes(), OUT_OF_QUEUE); - - L.push(start); - - after[start] = start; - before[start] = start; - degree[start] = -1; - status[start] = IN_QUEUE; - - while( ! L.empty() ) { - NodeID v = L.front(); - L.pop(); - - short current_status = status[v]; - status[v] = OUT_OF_QUEUE; - - if( current_status == INACTIVE ) continue; - - forall_out_edges(G, e, v) { - NodeID w = G.getEdgeTarget(e); - int delta = distance[w] - distance[v] - G.getEdgeWeight(e); - if(delta > 0) { - // dissassemble subtree - // in this case we disassemble the subtree looking for v - int new_distance = distance[w] - delta; - - int x = before[w]; - int y = w; - if( x != NULL_NODE) { - // in this case w is allready in the tree and we remove it / disassemble the subtree - for( int total_degree = 0; total_degree >= 0; y = after[y]) { - //disassemble the subtree - //w <-> .... <-> y <-> ... - if( y == (int) v ) { - parent[w] = v; - return w; // since parent[w] = v - - } else{ - distance[y] = distance[y] - delta; - before[y] = NULL_NODE; - total_degree += degree[y]; - - if( status[y] == ACTIVE ) { - status[y] = INACTIVE; - } - } - } - - // since we removed w from the shortest path tree - degree[parent[w]]--; - - // the old subtree - // x <-> [w <-> subtree]_is cut <-> y - // y is the vertex after the subtree of w - // afterwards: x <-> y - after[x] = y; - before[y] = x; - } - distance[w] = new_distance; - parent[w] = v; - - } - - // negative cycle is not found - // =================================================== - // take care of the rest of the shortest path Tree T - // =================================================== - if( before[w] == NULL_NODE && parent[w] == v) { - // in this case w was not in the shortest path tree - // so we integrate it - degree[v] ++; - degree[w] = -1; - - NodeID after_v = after[v]; - - // integrate w into the tree - after[v] = w; - before[w] = v; - after[w] = after_v; - before[after_v] = w; - // we now have v <-> w <-> after_v in the preorder tree representation - - // handle the queue - if( status[w] == OUT_OF_QUEUE ) { - L.push(w); - status[w] = IN_QUEUE; - } else { - status[w] = ACTIVE; - } - - - } - } endfor + // Goldberg spc-1.2 similar implementation using our data structures + int NULL_NODE = -1; + distance[start] = 0; + std::queue L; + + //doubly linked list of shortest path tree in preorder + short OUT_OF_QUEUE = 0; + short INACTIVE = 1; + short ACTIVE = 2; + short IN_QUEUE = 2; + + std::vector before(G.number_of_nodes(), NULL_NODE); + std::vector after(G.number_of_nodes()); + std::vector degree(G.number_of_nodes()); + std::vector status(G.number_of_nodes(), OUT_OF_QUEUE); + + L.push(start); + + after[start] = start; + before[start] = start; + degree[start] = -1; + status[start] = IN_QUEUE; + + while( ! L.empty() ) { + NodeID v = L.front(); + L.pop(); + + short current_status = status[v]; + status[v] = OUT_OF_QUEUE; + + if( current_status == INACTIVE ) continue; + + forall_out_edges(G, e, v) { + NodeID w = G.getEdgeTarget(e); + int delta = distance[w] - distance[v] - G.getEdgeWeight(e); + if(delta > 0) { + // dissassemble subtree + // in this case we disassemble the subtree looking for v + int new_distance = distance[w] - delta; + + int x = before[w]; + int y = w; + if( x != NULL_NODE) { + // in this case w is allready in the tree and we remove it / disassemble the subtree + for( int total_degree = 0; total_degree >= 0; y = after[y]) { + //disassemble the subtree + //w <-> .... <-> y <-> ... + if( y == (int) v ) { + parent[w] = v; + return w; // since parent[w] = v + + } else{ + distance[y] = distance[y] - delta; + before[y] = NULL_NODE; + total_degree += degree[y]; + + if( status[y] == ACTIVE ) { + status[y] = INACTIVE; + } + } + } + + // since we removed w from the shortest path tree + degree[parent[w]]--; + + // the old subtree + // x <-> [w <-> subtree]_is cut <-> y + // y is the vertex after the subtree of w + // afterwards: x <-> y + after[x] = y; + before[y] = x; } - return NULL_NODE; + distance[w] = new_distance; + parent[w] = v; + + } + + // negative cycle is not found + // =================================================== + // take care of the rest of the shortest path Tree T + // =================================================== + if( before[w] == NULL_NODE && parent[w] == v) { + // in this case w was not in the shortest path tree + // so we integrate it + degree[v] ++; + degree[w] = -1; + + NodeID after_v = after[v]; + + // integrate w into the tree + after[v] = w; + before[w] = v; + after[w] = after_v; + before[after_v] = w; + // we now have v <-> w <-> after_v in the preorder tree representation + + // handle the queue + if( status[w] == OUT_OF_QUEUE ) { + L.push(w); + status[w] = IN_QUEUE; + } else { + status[w] = ACTIVE; + } + + + } + } endfor +} + return NULL_NODE; } -bool cycle_search::negative_cycle_detection(graph_access & G, - NodeID & start, - std::vector & distance, - std::vector & parent, +bool cycle_search::negative_cycle_detection(graph_access & G, + NodeID & start, + std::vector & distance, + std::vector & parent, std::vector & cycle) { - timer timeR; - - int w = bellman_ford_with_subtree_disassembly_and_updates(G, start, distance, parent, cycle); - - if(w >= 0) { // found a cycle - // the edge yielding the cycle was (t,w) - NodeID t = parent[w]; - NodeID u = t; - - std::vector seen(G.number_of_nodes(), false); //use hashing? - seen[u] = true; - NodeID predecessor = parent[u]; - NodeID start_vertex; - - while( true ) { - if( seen[predecessor] ) { - start_vertex = predecessor; - break; - } - seen[predecessor] = true; - predecessor = parent[predecessor]; - } - - cycle.push_back(start_vertex); - predecessor = parent[start_vertex]; - while( predecessor != start_vertex) { - cycle.push_back(predecessor); - predecessor = parent[predecessor]; - } - cycle.push_back(start_vertex); - std::reverse(cycle.begin(), cycle.end()); - - total_time += timeR.elapsed(); - return true; - - } - - total_time += timeR.elapsed(); - return false; + timer timeR; + + int w = bellman_ford_with_subtree_disassembly_and_updates(G, start, distance, parent, cycle); + + if(w >= 0) { // found a cycle + // the edge yielding the cycle was (t,w) + NodeID t = parent[w]; + NodeID u = t; + + std::vector seen(G.number_of_nodes(), false); //use hashing? + seen[u] = true; + NodeID predecessor = parent[u]; + NodeID start_vertex; + + while( true ) { + if( seen[predecessor] ) { + start_vertex = predecessor; + break; + } + seen[predecessor] = true; + predecessor = parent[predecessor]; + } + + cycle.push_back(start_vertex); + predecessor = parent[start_vertex]; + while( predecessor != start_vertex) { + cycle.push_back(predecessor); + predecessor = parent[predecessor]; + } + cycle.push_back(start_vertex); + std::reverse(cycle.begin(), cycle.end()); + + total_time += timeR.elapsed(); + return true; + + } + + total_time += timeR.elapsed(); + return false; } -//preconditition: no negative cycles +//preconditition: no negative cycles bool cycle_search::find_zero_weight_cycle(graph_access & G, NodeID & start, std::vector & cycle) { - std::vector distance(G.number_of_nodes(), std::numeric_limits::max()/2); - std::vector parent(G.number_of_nodes(), std::numeric_limits::max()); - bool negative_weight_cycle = negative_cycle_detection(G, start, distance, parent, cycle); - if(!negative_weight_cycle) { - //now we try to return a random directed zero weight gain cycle - //therefore we use W(e) = d(u) + w(e) - d(v) - //and keep edges with weight 0 - graph_access W; - W.start_construction(G.number_of_nodes(), G.number_of_edges()); - - forall_nodes(G, node) { - NodeID shadow_node = W.new_node(); - W.setNodeWeight(shadow_node, G.getNodeWeight(node)); - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - EdgeWeight modified_edge_weight = G.getEdgeWeight(e) + distance[node] - distance[target]; - ASSERT_GEQ(modified_edge_weight, 0); - if(modified_edge_weight == 0) { - EdgeID shadow_edge = W.new_edge(shadow_node, target); - W.setEdgeWeight(shadow_edge, 0); - } - } endfor - } endfor - W.finish_construction(); - - strongly_connected_components scc; - std::vector comp_num(W.number_of_nodes()); - scc.strong_components(W, comp_num); - - //first check wether there are components with more then one vertex - std::vector comp_count(W.number_of_nodes(), 0); - forall_nodes(W, node) { - comp_count[comp_num[node]]++; - } endfor - - std::vector candidates; - forall_nodes(W, node) { - if(comp_count[comp_num[node]] > 1) { - candidates.push_back(node); - } - } endfor - - if(candidates.size() == 0) {return false;} - - //now pick a random start vertex - NodeID start_vertex_idx = random_functions::nextInt(0, candidates.size()-1); - NodeID start_vertex = candidates[start_vertex_idx]; - std::vector seen(W.number_of_nodes(), false); - std::vector list; - - NodeID successor = start_vertex; - NodeID comp_of_sv = comp_num[start_vertex]; - do { - - seen[successor] = true; - list.push_back(successor); - - std::vector same_comp_neighbors; - forall_out_edges(W, e, successor) { - NodeID target = W.getEdgeTarget(e); - if(comp_num[target] == (int)comp_of_sv) { - same_comp_neighbors.push_back(target); - } - } endfor - NodeID succ_id = random_functions::nextInt(0, same_comp_neighbors.size()-1); - - successor = same_comp_neighbors[succ_id]; - } while(seen[successor] == false); - - NodeID start_idx = 0; - for( unsigned i = 0; i < list.size(); i++) { - if(list[i] == successor) { - start_idx = i; - break; - } - } - - for( unsigned i = start_idx; i < list.size(); i++) { - cycle.push_back(list[i]); - } - cycle.push_back(successor); - - return true; + std::vector distance(G.number_of_nodes(), std::numeric_limits::max()/2); + std::vector parent(G.number_of_nodes(), std::numeric_limits::max()); + bool negative_weight_cycle = negative_cycle_detection(G, start, distance, parent, cycle); + if(!negative_weight_cycle) { + //now we try to return a random directed zero weight gain cycle + //therefore we use W(e) = d(u) + w(e) - d(v) + //and keep edges with weight 0 + graph_access W; + W.start_construction(G.number_of_nodes(), G.number_of_edges()); + + forall_nodes(G, node) { + NodeID shadow_node = W.new_node(); + W.setNodeWeight(shadow_node, G.getNodeWeight(node)); + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + EdgeWeight modified_edge_weight = G.getEdgeWeight(e) + distance[node] - distance[target]; + ASSERT_GEQ(modified_edge_weight, 0); + if(modified_edge_weight == 0) { + EdgeID shadow_edge = W.new_edge(shadow_node, target); + W.setEdgeWeight(shadow_edge, 0); + } + } endfor +} endfor +W.finish_construction(); + + strongly_connected_components scc; + std::vector comp_num(W.number_of_nodes()); + scc.strong_components(W, comp_num); + + //first check wether there are components with more then one vertex + std::vector comp_count(W.number_of_nodes(), 0); + forall_nodes(W, node) { + comp_count[comp_num[node]]++; + } endfor + + std::vector candidates; + forall_nodes(W, node) { + if(comp_count[comp_num[node]] > 1) { + candidates.push_back(node); + } + } endfor + + if(candidates.size() == 0) {return false;} + + //now pick a random start vertex + NodeID start_vertex_idx = random_functions::nextInt(0, candidates.size()-1); + NodeID start_vertex = candidates[start_vertex_idx]; + std::vector seen(W.number_of_nodes(), false); + std::vector list; + + NodeID successor = start_vertex; + NodeID comp_of_sv = comp_num[start_vertex]; + do { + + seen[successor] = true; + list.push_back(successor); + + std::vector same_comp_neighbors; + forall_out_edges(W, e, successor) { + NodeID target = W.getEdgeTarget(e); + if(comp_num[target] == (int)comp_of_sv) { + same_comp_neighbors.push_back(target); } - return false; + } endfor + NodeID succ_id = random_functions::nextInt(0, same_comp_neighbors.size()-1); + + successor = same_comp_neighbors[succ_id]; + } while(seen[successor] == false); + + NodeID start_idx = 0; + for( unsigned i = 0; i < list.size(); i++) { + if(list[i] == successor) { + start_idx = i; + break; + } + } + + for( unsigned i = start_idx; i < list.size(); i++) { + cycle.push_back(list[i]); + } + cycle.push_back(successor); + + return true; + } + return false; } +} \ No newline at end of file diff --git a/parallel/modified_kahip/lib/algorithms/cycle_search.h b/parallel/modified_kahip/lib/algorithms/cycle_search.h index 87af2359..4d1b2fd2 100644 --- a/parallel/modified_kahip/lib/algorithms/cycle_search.h +++ b/parallel/modified_kahip/lib/algorithms/cycle_search.h @@ -9,36 +9,33 @@ #define CYCLE_SEARCH_IO23844C #include "data_structure/graph_access.h" - +namespace kahip::modified { class cycle_search { public: - cycle_search(); - virtual ~cycle_search(); + void find_random_cycle(graph_access & G, std::vector & cycle); - void find_random_cycle(graph_access & G, std::vector & cycle); + //returns true if a negative cycle was found, else false + bool find_negative_cycle(graph_access & G, NodeID & start, std::vector & cycle); - //returns true if a negative cycle was found, else false - bool find_negative_cycle(graph_access & G, NodeID & start, std::vector & cycle); - - bool find_zero_weight_cycle(graph_access & G, NodeID & start, std::vector & cycle); + bool find_zero_weight_cycle(graph_access & G, NodeID & start, std::vector & cycle); - bool find_shortest_path(graph_access & G, NodeID & start, NodeID & dest, std::vector & cycle); + bool find_shortest_path(graph_access & G, NodeID & start, NodeID & dest, std::vector & cycle); - static double total_time; + static double total_time; private: - bool negative_cycle_detection(graph_access & G, - NodeID & start, - std::vector & distance, - std::vector & parent, - std::vector & cycle); - - int bellman_ford_with_subtree_disassembly_and_updates(graph_access & G, - NodeID & start, - std::vector & distance, - std::vector & parent, - std::vector & cycle); + bool negative_cycle_detection(graph_access & G, + NodeID & start, + std::vector & distance, + std::vector & parent, + std::vector & cycle); + + int bellman_ford_with_subtree_disassembly_and_updates(graph_access & G, + NodeID & start, + std::vector & distance, + std::vector & parent, + std::vector & cycle); }; - +} #endif /* end of include guard: CYCLE_SEARCH_IO23844C */ diff --git a/parallel/modified_kahip/lib/algorithms/strongly_connected_components.cpp b/parallel/modified_kahip/lib/algorithms/strongly_connected_components.cpp index 0f995dca..c3f14227 100644 --- a/parallel/modified_kahip/lib/algorithms/strongly_connected_components.cpp +++ b/parallel/modified_kahip/lib/algorithms/strongly_connected_components.cpp @@ -9,73 +9,66 @@ #include #include "strongly_connected_components.h" - -strongly_connected_components::strongly_connected_components() { - -} - -strongly_connected_components::~strongly_connected_components() { - -} +namespace kahip::modified { int strongly_connected_components::strong_components( graph_access & G, std::vector & comp_num) { - std::stack unfinished; - std::stack roots; + std::stack unfinished; + std::stack roots; - std::vector dfsnum(G.number_of_nodes(), -1); - m_dfscount = 0; - m_comp_count = 0; + std::vector dfsnum(G.number_of_nodes(), -1); + m_dfscount = 0; + m_comp_count = 0; - forall_nodes(G, node) { - comp_num[node] = -1; - } endfor + forall_nodes(G, node) { + comp_num[node] = -1; + } endfor - forall_nodes(G, node) { - if(dfsnum[node] == -1) { - scc_dfs(node, G, dfsnum, comp_num, unfinished, roots); - } - } endfor - return m_comp_count; + forall_nodes(G, node) { + if(dfsnum[node] == -1) { + scc_dfs(node, G, dfsnum, comp_num, unfinished, roots); + } + } endfor + return m_comp_count; } -void strongly_connected_components::scc_dfs(NodeID node, graph_access & G, - std::vector & dfsnum, +void strongly_connected_components::scc_dfs(NodeID node, graph_access & G, + std::vector & dfsnum, std::vector & comp_num, - std::stack & unfinished, - std::stack & roots){ - dfsnum[node] = m_dfscount++; - - //make node a tentative scc of its own - unfinished.push(node); - roots.push(node); - - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - //explore edge (node, target) - if(dfsnum[target] == -1) { - scc_dfs(target, G, dfsnum, comp_num, unfinished, roots); - } else if( comp_num[target] == -1) { - //merge scc's - while( dfsnum[roots.top()] > dfsnum[target] ) roots.pop(); - } - - } endfor - - //return from call of node node - NodeID w; - if(node == roots.top()) { - do { - w = unfinished.top(); - unfinished.pop(); - comp_num[w] = m_comp_count; - } while( w != node ); - m_comp_count++; - roots.pop(); - } + std::stack & unfinished, + std::stack & roots){ + dfsnum[node] = m_dfscount++; + + //make node a tentative scc of its own + unfinished.push(node); + roots.push(node); + + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + //explore edge (node, target) + if(dfsnum[target] == -1) { + scc_dfs(target, G, dfsnum, comp_num, unfinished, roots); + } else if( comp_num[target] == -1) { + //merge scc's + while( dfsnum[roots.top()] > dfsnum[target] ) roots.pop(); + } + + } endfor + + //return from call of node node + NodeID w; + if(node == roots.top()) { + do { + w = unfinished.top(); + unfinished.pop(); + comp_num[w] = m_comp_count; + } while( w != node ); + m_comp_count++; + roots.pop(); + } } - +} diff --git a/parallel/modified_kahip/lib/algorithms/strongly_connected_components.h b/parallel/modified_kahip/lib/algorithms/strongly_connected_components.h index 0350dd6d..bfe477fc 100644 --- a/parallel/modified_kahip/lib/algorithms/strongly_connected_components.h +++ b/parallel/modified_kahip/lib/algorithms/strongly_connected_components.h @@ -13,23 +13,21 @@ #include "data_structure/graph_access.h" #include "definitions.h" - +namespace kahip::modified { class strongly_connected_components { public: - strongly_connected_components(); - virtual ~strongly_connected_components(); - int strong_components( graph_access & G, std::vector & comp_num); - - void scc_dfs(NodeID node, graph_access & G, - std::vector & dfsnum, - std::vector & comp_num, - std::stack & unfinished, - std::stack & roots); + int strong_components( graph_access & G, std::vector & comp_num); + + void scc_dfs(NodeID node, graph_access & G, + std::vector & dfsnum, + std::vector & comp_num, + std::stack & unfinished, + std::stack & roots); private: - int m_dfscount; - int m_comp_count; + int m_dfscount = 0; + int m_comp_count = 0; }; - +} #endif /* end of include guard: STRONGLY_CONNECTED_COMPONENTS_7ZJ8233R */ diff --git a/parallel/modified_kahip/lib/algorithms/topological_sort.cpp b/parallel/modified_kahip/lib/algorithms/topological_sort.cpp index 7a602c72..a0740b3b 100644 --- a/parallel/modified_kahip/lib/algorithms/topological_sort.cpp +++ b/parallel/modified_kahip/lib/algorithms/topological_sort.cpp @@ -9,47 +9,41 @@ #include "random_functions.h" #include "topological_sort.h" - -topological_sort::topological_sort() { - -} - -topological_sort::~topological_sort() { - -} +namespace kahip::modified { void topological_sort::sort( graph_access & G, std::vector & sorted_sequence) { - std::vector dfsnum(G.number_of_nodes(), -1); - int dfscount = 0; + std::vector dfsnum(G.number_of_nodes(), -1); + int dfscount = 0; - std::vector nodes(G.number_of_nodes()); - random_functions::permutate_vector_good(nodes, true); + std::vector nodes(G.number_of_nodes()); + random_functions::permutate_vector_good(nodes, true); - forall_nodes(G, node) { - NodeID curNode = nodes[node]; - if(dfsnum[curNode] == -1) { - sort_dfs(curNode, G, dfsnum, dfscount, sorted_sequence); - } - } endfor + forall_nodes(G, node) { + NodeID curNode = nodes[node]; + if(dfsnum[curNode] == -1) { + sort_dfs(curNode, G, dfsnum, dfscount, sorted_sequence); + } + } endfor - std::reverse(sorted_sequence.begin(), sorted_sequence.end()); + std::reverse(sorted_sequence.begin(), sorted_sequence.end()); } -void topological_sort::sort_dfs(NodeID node, graph_access & G, - std::vector & dfsnum, +void topological_sort::sort_dfs(NodeID node, graph_access & G, + std::vector & dfsnum, int & dfscount, - std::vector & sorted_sequence){ - - dfsnum[node] = dfscount++; - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - //explore edge (node, target) - if(dfsnum[target] == -1) { - sort_dfs(target, G, dfsnum, dfscount, sorted_sequence); - } - } endfor - - //return from call of node node - sorted_sequence.push_back(node); + std::vector & sorted_sequence){ + + dfsnum[node] = dfscount++; + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + //explore edge (node, target) + if(dfsnum[target] == -1) { + sort_dfs(target, G, dfsnum, dfscount, sorted_sequence); + } + } endfor + + //return from call of node node + sorted_sequence.push_back(node); } +} \ No newline at end of file diff --git a/parallel/modified_kahip/lib/algorithms/topological_sort.h b/parallel/modified_kahip/lib/algorithms/topological_sort.h index 56701add..fffe03e5 100644 --- a/parallel/modified_kahip/lib/algorithms/topological_sort.h +++ b/parallel/modified_kahip/lib/algorithms/topological_sort.h @@ -13,19 +13,16 @@ #include "data_structure/graph_access.h" #include "definitions.h" - +namespace kahip::modified { class topological_sort { public: - topological_sort(); - virtual ~topological_sort(); - - void sort( graph_access & SG, std::vector & sorted_sequence); + void sort( graph_access & SG, std::vector & sorted_sequence); - void sort_dfs(NodeID node, graph_access & G, - std::vector & dfsnum, - int & dfscount, - std::vector & sorted_sequence); + void sort_dfs(NodeID node, graph_access & G, + std::vector & dfsnum, + int & dfscount, + std::vector & sorted_sequence); }; - +} #endif /* end of include guard: TOPOLOGICAL_SORT_GB9FC2CZ */ diff --git a/parallel/modified_kahip/lib/data_structure/graph_access.h b/parallel/modified_kahip/lib/data_structure/graph_access.h index d7cfa473..151eba29 100644 --- a/parallel/modified_kahip/lib/data_structure/graph_access.h +++ b/parallel/modified_kahip/lib/data_structure/graph_access.h @@ -14,128 +14,128 @@ #include #include "definitions.h" - +namespace kahip::modified { struct Node { - EdgeID firstEdge; - NodeWeight weight; + EdgeID firstEdge; + NodeWeight weight; }; struct Edge { - NodeID target; - EdgeWeight weight; + NodeID target; + EdgeWeight weight; }; struct refinementNode { - PartitionID partitionIndex; - //Count queueIndex; + PartitionID partitionIndex; + //Count queueIndex; }; struct coarseningEdge { - EdgeRatingType rating; + EdgeRatingType rating; }; class graph_access; //construction etc. is encapsulated in basicGraph / access to properties etc. is encapsulated in graph_access class basicGraph { - friend class graph_access; + friend class graph_access; public: - basicGraph() : m_building_graph(false) { - } + basicGraph() : m_building_graph(false) { + } private: - //methods only to be used by friend class - EdgeID number_of_edges() { - return m_edges.size(); - } - - NodeID number_of_nodes() { - return m_nodes.size()-1; - } - - inline EdgeID get_first_edge(const NodeID & node) { - return m_nodes[node].firstEdge; - } - - inline EdgeID get_first_invalid_edge(const NodeID & node) { - return m_nodes[node+1].firstEdge; - } - - // construction of the graph - void start_construction(NodeID n, EdgeID m) { - m_building_graph = true; - node = 0; - e = 0; - m_last_source = -1; - - //resizes property arrays - m_nodes.resize(n+1); - m_refinement_node_props.resize(n+1); - m_edges.resize(m); - m_coarsening_edge_props.resize(m); - - m_nodes[node].firstEdge = e; - } - - EdgeID new_edge(NodeID source, NodeID target) { - ASSERT_TRUE(m_building_graph); - ASSERT_TRUE(e < m_edges.size()); - - m_edges[e].target = target; - EdgeID e_bar = e; - ++e; - - ASSERT_TRUE(source+1 < m_nodes.size()); - m_nodes[source+1].firstEdge = e; - - //fill isolated sources at the end - if ((NodeID)(m_last_source+1) < source) { - for (NodeID i = source; i>(NodeID)(m_last_source+1); i--) { - m_nodes[i].firstEdge = m_nodes[m_last_source+1].firstEdge; - } + //methods only to be used by friend class + EdgeID number_of_edges() { + return m_edges.size(); + } + + NodeID number_of_nodes() { + return m_nodes.size()-1; + } + + inline EdgeID get_first_edge(const NodeID & node) { + return m_nodes[node].firstEdge; + } + + inline EdgeID get_first_invalid_edge(const NodeID & node) { + return m_nodes[node+1].firstEdge; + } + + // construction of the graph + void start_construction(NodeID n, EdgeID m) { + m_building_graph = true; + node = 0; + e = 0; + m_last_source = -1; + + //resizes property arrays + m_nodes.resize(n+1); + m_refinement_node_props.resize(n+1); + m_edges.resize(m); + m_coarsening_edge_props.resize(m); + + m_nodes[node].firstEdge = e; } - m_last_source = source; - return e_bar; - } - - NodeID new_node() { - ASSERT_TRUE(m_building_graph); - return node++; - } - - void finish_construction() { - // inert dummy node - m_nodes.resize(node+1); - m_refinement_node_props.resize(node+1); - - m_edges.resize(e); - m_coarsening_edge_props.resize(e); - - m_building_graph = false; - - //fill isolated sources at the end - if ((unsigned int)(m_last_source) != node-1) { - //in that case at least the last node was an isolated node - for (NodeID i = node; i>(unsigned int)(m_last_source+1); i--) { - m_nodes[i].firstEdge = m_nodes[m_last_source+1].firstEdge; + + EdgeID new_edge(NodeID source, NodeID target) { + ASSERT_TRUE(m_building_graph); + ASSERT_TRUE(e < m_edges.size()); + + m_edges[e].target = target; + EdgeID e_bar = e; + ++e; + + ASSERT_TRUE(source+1 < m_nodes.size()); + m_nodes[source+1].firstEdge = e; + + //fill isolated sources at the end + if ((NodeID)(m_last_source+1) < source) { + for (NodeID i = source; i>(NodeID)(m_last_source+1); i--) { + m_nodes[i].firstEdge = m_nodes[m_last_source+1].firstEdge; + } } + m_last_source = source; + return e_bar; } - } - - // %%%%%%%%%%%%%%%%%%% DATA %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - // split properties for coarsening and uncoarsening - std::vector m_nodes; - std::vector m_edges; - - std::vector m_refinement_node_props; - std::vector m_coarsening_edge_props; - - // construction properties - bool m_building_graph; - int m_last_source; - NodeID node; //current node that is constructed - EdgeID e; //current edge that is constructed + + NodeID new_node() { + ASSERT_TRUE(m_building_graph); + return node++; + } + + void finish_construction() { + // inert dummy node + m_nodes.resize(node+1); + m_refinement_node_props.resize(node+1); + + m_edges.resize(e); + m_coarsening_edge_props.resize(e); + + m_building_graph = false; + + //fill isolated sources at the end + if ((unsigned int)(m_last_source) != node-1) { + //in that case at least the last node was an isolated node + for (NodeID i = node; i>(unsigned int)(m_last_source+1); i--) { + m_nodes[i].firstEdge = m_nodes[m_last_source+1].firstEdge; + } + } + } + + // %%%%%%%%%%%%%%%%%%% DATA %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + // split properties for coarsening and uncoarsening + std::vector m_nodes; + std::vector m_edges; + + std::vector m_refinement_node_props; + std::vector m_coarsening_edge_props; + + // construction properties + bool m_building_graph; + int m_last_source; + NodeID node; //current node that is constructed + EdgeID e; //current edge that is constructed }; //makros - graph access @@ -150,73 +150,73 @@ class complete_boundary; class graph_access { friend class complete_boundary; - public: - graph_access() { m_max_degree_computed = false; m_max_degree = 0; graphref = new basicGraph();} - virtual ~graph_access(){ delete graphref; }; +public: + graph_access() { m_max_degree_computed = false; m_max_degree = 0; graphref = new basicGraph();} + virtual ~graph_access(){ delete graphref; }; - /* ============================================================= */ - /* build methods */ - /* ============================================================= */ - void start_construction(NodeID nodes, EdgeID edges); - NodeID new_node(); - EdgeID new_edge(NodeID source, NodeID target); - void finish_construction(); + /* ============================================================= */ + /* build methods */ + /* ============================================================= */ + void start_construction(NodeID nodes, EdgeID edges); + NodeID new_node(); + EdgeID new_edge(NodeID source, NodeID target); + void finish_construction(); - /* ============================================================= */ - /* graph access methods */ - /* ============================================================= */ - NodeID number_of_nodes(); - EdgeID number_of_edges(); + /* ============================================================= */ + /* graph access methods */ + /* ============================================================= */ + NodeID number_of_nodes(); + EdgeID number_of_edges(); - EdgeID get_first_edge(NodeID node); - EdgeID get_first_invalid_edge(NodeID node); + EdgeID get_first_edge(NodeID node); + EdgeID get_first_invalid_edge(NodeID node); - PartitionID get_partition_count(); - void set_partition_count(PartitionID count); + PartitionID get_partition_count(); + void set_partition_count(PartitionID count); - PartitionID getPartitionIndex(NodeID node); - void setPartitionIndex(NodeID node, PartitionID id); + PartitionID getPartitionIndex(NodeID node); + void setPartitionIndex(NodeID node, PartitionID id); - PartitionID getSecondPartitionIndex(NodeID node); - void setSecondPartitionIndex(NodeID node, PartitionID id); + PartitionID getSecondPartitionIndex(NodeID node); + void setSecondPartitionIndex(NodeID node, PartitionID id); - //to be called if combine in meta heuristic is used - void resizeSecondPartitionIndex(unsigned no_nodes); + //to be called if combine in meta heuristic is used + void resizeSecondPartitionIndex(unsigned no_nodes); - NodeWeight getNodeWeight(NodeID node); - void setNodeWeight(NodeID node, NodeWeight weight); + NodeWeight getNodeWeight(NodeID node); + void setNodeWeight(NodeID node, NodeWeight weight); - EdgeWeight getNodeDegree(NodeID node); - EdgeWeight getWeightedNodeDegree(NodeID node); - EdgeWeight getMaxDegree(); + EdgeWeight getNodeDegree(NodeID node); + EdgeWeight getWeightedNodeDegree(NodeID node); + EdgeWeight getMaxDegree(); - EdgeWeight getEdgeWeight(EdgeID edge); - void setEdgeWeight(EdgeID edge, EdgeWeight weight); + EdgeWeight getEdgeWeight(EdgeID edge); + void setEdgeWeight(EdgeID edge, EdgeWeight weight); - NodeID getEdgeTarget(EdgeID edge); + NodeID getEdgeTarget(EdgeID edge); - EdgeRatingType getEdgeRating(EdgeID edge); - void setEdgeRating(EdgeID edge, EdgeRatingType rating); + EdgeRatingType getEdgeRating(EdgeID edge); + void setEdgeRating(EdgeID edge, EdgeRatingType rating); - int* UNSAFE_metis_style_xadj_array(); - int* UNSAFE_metis_style_adjncy_array(); + int* UNSAFE_metis_style_xadj_array(); + int* UNSAFE_metis_style_adjncy_array(); - int* UNSAFE_metis_style_vwgt_array(); - int* UNSAFE_metis_style_adjwgt_array(); + int* UNSAFE_metis_style_vwgt_array(); + int* UNSAFE_metis_style_adjwgt_array(); - int build_from_metis(int n, int* xadj, int* adjncy); - int build_from_metis_weighted(int n, int* xadj, int* adjncy, int * vwgt, int* adjwgt); + int build_from_metis(int n, int* xadj, int* adjncy); + int build_from_metis_weighted(int n, int* xadj, int* adjncy, int * vwgt, int* adjwgt); - //void set_node_queue_index(NodeID node, Count queue_index); - //Count get_node_queue_index(NodeID node); + //void set_node_queue_index(NodeID node, Count queue_index); + //Count get_node_queue_index(NodeID node); - void copy(graph_access & Gcopy); - private: - basicGraph * graphref; - bool m_max_degree_computed; - unsigned int m_partition_count; - EdgeWeight m_max_degree; - std::vector m_second_partition_index; + void copy(graph_access & Gcopy); +private: + basicGraph * graphref; + bool m_max_degree_computed; + unsigned int m_partition_count; + EdgeWeight m_max_degree; + std::vector m_second_partition_index; }; /* graph build methods */ @@ -300,49 +300,49 @@ inline void graph_access::setPartitionIndex(NodeID node, PartitionID id) { inline NodeWeight graph_access::getNodeWeight(NodeID node){ #ifdef NDEBUG - return graphref->m_nodes[node].weight; + return graphref->m_nodes[node].weight; #else - return graphref->m_nodes.at(node).weight; + return graphref->m_nodes.at(node).weight; #endif } inline void graph_access::setNodeWeight(NodeID node, NodeWeight weight){ #ifdef NDEBUG - graphref->m_nodes[node].weight = weight; + graphref->m_nodes[node].weight = weight; #else - graphref->m_nodes.at(node).weight = weight; + graphref->m_nodes.at(node).weight = weight; #endif } inline EdgeWeight graph_access::getEdgeWeight(EdgeID edge){ #ifdef NDEBUG - return graphref->m_edges[edge].weight; + return graphref->m_edges[edge].weight; #else - return graphref->m_edges.at(edge).weight; + return graphref->m_edges.at(edge).weight; #endif } inline void graph_access::setEdgeWeight(EdgeID edge, EdgeWeight weight){ #ifdef NDEBUG - graphref->m_edges[edge].weight = weight; + graphref->m_edges[edge].weight = weight; #else - graphref->m_edges.at(edge).weight = weight; + graphref->m_edges.at(edge).weight = weight; #endif } inline NodeID graph_access::getEdgeTarget(EdgeID edge){ #ifdef NDEBUG - return graphref->m_edges[edge].target; + return graphref->m_edges[edge].target; #else - return graphref->m_edges.at(edge).target; + return graphref->m_edges.at(edge).target; #endif } inline EdgeRatingType graph_access::getEdgeRating(EdgeID edge) { #ifdef NDEBUG - return graphref->m_coarsening_edge_props[edge].rating; + return graphref->m_coarsening_edge_props[edge].rating; #else - return graphref->m_coarsening_edge_props.at(edge).rating; + return graphref->m_coarsening_edge_props.at(edge).rating; #endif } @@ -359,10 +359,10 @@ inline EdgeWeight graph_access::getNodeDegree(NodeID node) { } inline EdgeWeight graph_access::getWeightedNodeDegree(NodeID node) { - EdgeWeight degree = 0; - for( unsigned e = graphref->m_nodes[node].firstEdge; e < graphref->m_nodes[node+1].firstEdge; ++e) { - degree += getEdgeWeight(e); - } + EdgeWeight degree = 0; + for( unsigned e = graphref->m_nodes[node].firstEdge; e < graphref->m_nodes[node+1].firstEdge; ++e) { + degree += getEdgeWeight(e); + } return degree; } @@ -496,5 +496,5 @@ inline void graph_access::copy(graph_access & G_bar) { G_bar.finish_construction(); } - +} #endif /* end of include guard: GRAPH_ACCESS_EFRXO4X2 */ diff --git a/parallel/modified_kahip/lib/data_structure/graph_hierarchy.cpp b/parallel/modified_kahip/lib/data_structure/graph_hierarchy.cpp index cde29410..9c1f903d 100644 --- a/parallel/modified_kahip/lib/data_structure/graph_hierarchy.cpp +++ b/parallel/modified_kahip/lib/data_structure/graph_hierarchy.cpp @@ -6,83 +6,84 @@ *****************************************************************************/ #include "graph_hierarchy.h" - -graph_hierarchy::graph_hierarchy() : m_current_coarser_graph(NULL), +namespace kahip::modified { +graph_hierarchy::graph_hierarchy() : m_current_coarser_graph(NULL), m_current_coarse_mapping(NULL){ } graph_hierarchy::~graph_hierarchy() { - for( unsigned i = 0; i < m_to_delete_mappings.size(); i++) { - if(m_to_delete_mappings[i] != NULL) - delete m_to_delete_mappings[i]; - } - - for( unsigned i = 0; i+1 < m_to_delete_hierachies.size(); i++) { - if(m_to_delete_hierachies[i] != NULL) - delete m_to_delete_hierachies[i]; - } + for( unsigned i = 0; i < m_to_delete_mappings.size(); i++) { + if(m_to_delete_mappings[i] != NULL) + delete m_to_delete_mappings[i]; + } + + for( unsigned i = 0; i+1 < m_to_delete_hierachies.size(); i++) { + if(m_to_delete_hierachies[i] != NULL) + delete m_to_delete_hierachies[i]; + } } void graph_hierarchy::push_back(graph_access * G, CoarseMapping * coarse_mapping) { - m_the_graph_hierarchy.push(G); - m_the_mappings.push(coarse_mapping); - m_to_delete_mappings.push_back(coarse_mapping); - m_coarsest_graph = G; + m_the_graph_hierarchy.push(G); + m_the_mappings.push(coarse_mapping); + m_to_delete_mappings.push_back(coarse_mapping); + m_coarsest_graph = G; } graph_access* graph_hierarchy::pop_finer_and_project() { - graph_access* finer = pop_coarsest(); - - CoarseMapping* coarse_mapping = m_the_mappings.top(); // mapps finer to coarser nodes - m_the_mappings.pop(); - - if(finer == m_coarsest_graph) { - m_current_coarser_graph = finer; - finer = pop_coarsest(); - finer->set_partition_count(m_current_coarser_graph->get_partition_count()); - - coarse_mapping = m_the_mappings.top(); - m_the_mappings.pop(); - } - - ASSERT_EQ(m_the_graph_hierarchy.size(), m_the_mappings.size()); - - //perform projection - graph_access& fRef = *finer; - graph_access& cRef = *m_current_coarser_graph; - forall_nodes(fRef, n) { - NodeID coarser_node = (*coarse_mapping)[n]; - PartitionID coarser_partition_id = cRef.getPartitionIndex(coarser_node); - fRef.setPartitionIndex(n, coarser_partition_id); - } endfor - - m_current_coarse_mapping = coarse_mapping; - finer->set_partition_count(m_current_coarser_graph->get_partition_count()); - m_current_coarser_graph = finer; - - return finer; + graph_access* finer = pop_coarsest(); + + CoarseMapping* coarse_mapping = m_the_mappings.top(); // mapps finer to coarser nodes + m_the_mappings.pop(); + + if(finer == m_coarsest_graph) { + m_current_coarser_graph = finer; + finer = pop_coarsest(); + finer->set_partition_count(m_current_coarser_graph->get_partition_count()); + + coarse_mapping = m_the_mappings.top(); + m_the_mappings.pop(); + } + + ASSERT_EQ(m_the_graph_hierarchy.size(), m_the_mappings.size()); + + //perform projection + graph_access& fRef = *finer; + graph_access& cRef = *m_current_coarser_graph; + forall_nodes(fRef, n) { + NodeID coarser_node = (*coarse_mapping)[n]; + PartitionID coarser_partition_id = cRef.getPartitionIndex(coarser_node); + fRef.setPartitionIndex(n, coarser_partition_id); + } endfor + + m_current_coarse_mapping = coarse_mapping; + finer->set_partition_count(m_current_coarser_graph->get_partition_count()); + m_current_coarser_graph = finer; + + return finer; } CoarseMapping * graph_hierarchy::get_mapping_of_current_finer() { - return m_current_coarse_mapping; + return m_current_coarse_mapping; } graph_access* graph_hierarchy::get_coarsest( ) { - return m_coarsest_graph; + return m_coarsest_graph; } graph_access* graph_hierarchy::pop_coarsest( ) { - graph_access* current_coarsest = m_the_graph_hierarchy.top(); - m_the_graph_hierarchy.pop(); - return current_coarsest; + graph_access* current_coarsest = m_the_graph_hierarchy.top(); + m_the_graph_hierarchy.pop(); + return current_coarsest; } bool graph_hierarchy::isEmpty( ) { - ASSERT_EQ(m_the_graph_hierarchy.size(), m_the_mappings.size()); - return m_the_graph_hierarchy.empty(); + ASSERT_EQ(m_the_graph_hierarchy.size(), m_the_mappings.size()); + return m_the_graph_hierarchy.empty(); } unsigned int graph_hierarchy::size() { - return m_the_graph_hierarchy.size(); + return m_the_graph_hierarchy.size(); } +} \ No newline at end of file diff --git a/parallel/modified_kahip/lib/data_structure/graph_hierarchy.h b/parallel/modified_kahip/lib/data_structure/graph_hierarchy.h index d1195eb8..2a04d493 100644 --- a/parallel/modified_kahip/lib/data_structure/graph_hierarchy.h +++ b/parallel/modified_kahip/lib/data_structure/graph_hierarchy.h @@ -11,32 +11,32 @@ #include #include "graph_access.h" - +namespace kahip::modified { class graph_hierarchy { public: - graph_hierarchy( ); - virtual ~graph_hierarchy(); - - void push_back(graph_access * G, CoarseMapping * coarse_mapping); - - graph_access * pop_finer_and_project(); - graph_access * get_coarsest(); - CoarseMapping * get_mapping_of_current_finer(); - - bool isEmpty(); - unsigned int size(); + graph_hierarchy( ); + virtual ~graph_hierarchy(); + + void push_back(graph_access * G, CoarseMapping * coarse_mapping); + + graph_access * pop_finer_and_project(); + graph_access * get_coarsest(); + CoarseMapping * get_mapping_of_current_finer(); + + bool isEmpty(); + unsigned int size(); private: - //private functions - graph_access * pop_coarsest(); - - std::stack m_the_graph_hierarchy; - std::stack m_the_mappings; - std::vector m_to_delete_mappings; - std::vector m_to_delete_hierachies; - graph_access * m_current_coarser_graph; - graph_access * m_coarsest_graph; - CoarseMapping * m_current_coarse_mapping; + //private functions + graph_access * pop_coarsest(); + + std::stack m_the_graph_hierarchy; + std::stack m_the_mappings; + std::vector m_to_delete_mappings; + std::vector m_to_delete_hierachies; + graph_access * m_current_coarser_graph; + graph_access * m_coarsest_graph; + CoarseMapping * m_current_coarse_mapping; }; - +} #endif /* end of include guard: GRAPH_HIERACHY_UMHG74CO */ diff --git a/parallel/modified_kahip/lib/data_structure/matrix/matrix.h b/parallel/modified_kahip/lib/data_structure/matrix/matrix.h index bee2fd81..dd85dff7 100644 --- a/parallel/modified_kahip/lib/data_structure/matrix/matrix.h +++ b/parallel/modified_kahip/lib/data_structure/matrix/matrix.h @@ -7,16 +7,23 @@ #ifndef MATRIX_BHHZ9T7P #define MATRIX_BHHZ9T7P - +namespace kahip::modified { class matrix { public: - matrix(unsigned int dim_x, unsigned int dim_y) {}; - matrix() {}; - virtual ~matrix() {}; + matrix(unsigned int dim_x, unsigned int dim_y) {}; + matrix() = default; // Default constructor + virtual ~matrix() = default; // Default destructor + + // Explicitly default the other special member functions + matrix(const matrix&) = default; // Copy constructor + matrix& operator=(const matrix&) = default; // Copy assignment operator + matrix(matrix&&) = default; // Move constructor + matrix& operator=(matrix&&) = default; // Move assignment operator - virtual int get_xy(unsigned int x, unsigned int y) = 0; - virtual void set_xy(unsigned int x, unsigned int y, int value) = 0; + virtual int get_xy(unsigned int x, unsigned int y) = 0; + virtual void set_xy(unsigned int x, unsigned int y, int value) = 0; }; +} #endif /* end of include guard: MATRIX_BHHZ9T7P */ diff --git a/parallel/modified_kahip/lib/data_structure/matrix/normal_matrix.h b/parallel/modified_kahip/lib/data_structure/matrix/normal_matrix.h index 6c9b4fba..e249747c 100644 --- a/parallel/modified_kahip/lib/data_structure/matrix/normal_matrix.h +++ b/parallel/modified_kahip/lib/data_structure/matrix/normal_matrix.h @@ -9,7 +9,7 @@ #define NORMAL_MATRIX_DAUJ4JMM #include "matrix.h" - +namespace kahip::modified { class normal_matrix : public matrix { public: normal_matrix(unsigned int dim_x, unsigned int dim_y, int lazy_init_val = 0) : m_dim_x (dim_x), @@ -17,18 +17,17 @@ class normal_matrix : public matrix { m_lazy_init_val ( lazy_init_val ) { m_internal_matrix.resize(m_dim_x); //allocate the rest lazy }; - virtual ~normal_matrix() {}; - inline int get_xy(unsigned int x, unsigned int y) { - if( m_internal_matrix[x].size() == 0 ) { + int get_xy(unsigned int x, unsigned int y) override { + if( m_internal_matrix[x].empty() ) { return m_lazy_init_val; } return m_internal_matrix[x][y]; }; - inline void set_xy(unsigned int x, unsigned int y, int value) { + void set_xy(unsigned int x, unsigned int y, int value) override { //resize the fields lazy - if( m_internal_matrix[x].size() == 0 ) { + if( m_internal_matrix[x].empty() ) { m_internal_matrix[x].resize(m_dim_y); for( unsigned y_1 = 0; y_1 < m_dim_y; y_1++) { m_internal_matrix[x][y_1] = m_lazy_init_val; @@ -42,6 +41,6 @@ class normal_matrix : public matrix { unsigned int m_dim_x, m_dim_y; int m_lazy_init_val; }; - +} #endif /* end of include guard: NORMAL_MATRIX_DAUJ4JMM */ diff --git a/parallel/modified_kahip/lib/data_structure/priority_queues/bucket_pq.h b/parallel/modified_kahip/lib/data_structure/priority_queues/bucket_pq.h index ffe0dd6a..2093193b 100644 --- a/parallel/modified_kahip/lib/data_structure/priority_queues/bucket_pq.h +++ b/parallel/modified_kahip/lib/data_structure/priority_queues/bucket_pq.h @@ -12,36 +12,35 @@ #include #include "priority_queue_interface.h" - +namespace kahip::modified { class bucket_pq : public priority_queue_interface { - public: - bucket_pq( const EdgeWeight & gain_span ); - - virtual ~bucket_pq() {}; - - NodeID size(); - void insert(NodeID id, Gain gain); - bool empty(); - - Gain maxValue(); - NodeID maxElement(); - NodeID deleteMax(); - - void decreaseKey(NodeID node, Gain newGain); - void increaseKey(NodeID node, Gain newGain); - - void changeKey(NodeID element, Gain newKey); - Gain getKey(NodeID element); - void deleteNode(NodeID node); - - bool contains(NodeID node); - private: - NodeID m_elements; - EdgeWeight m_gain_span; - unsigned m_max_idx; //points to the non-empty bucket with the largest gain - - std::unordered_map > m_queue_index; - std::vector< std::vector > m_buckets; +public: + explicit bucket_pq( const EdgeWeight & gain_span ); + + + NodeID size() override; + void insert(NodeID node, Gain gain) override; + bool empty() override; + + Gain maxValue() override; + NodeID maxElement() override; + NodeID deleteMax() override; + + void decreaseKey(NodeID node, Gain new_gain) override; + void increaseKey(NodeID node, Gain new_gain) override; + + void changeKey(NodeID node, Gain new_gain) override; + Gain getKey(NodeID node) override; + void deleteNode(NodeID node) override; + + bool contains(NodeID node) override; +private: + NodeID m_elements; + EdgeWeight m_gain_span; + unsigned m_max_idx; //points to the non-empty bucket with the largest gain + + std::unordered_map > m_queue_index; + std::vector< std::vector > m_buckets; }; inline bucket_pq::bucket_pq( const EdgeWeight & gain_span_input ) { @@ -53,16 +52,16 @@ inline bucket_pq::bucket_pq( const EdgeWeight & gain_span_input ) { } inline NodeID bucket_pq::size() { - return m_elements; + return m_elements; } inline void bucket_pq::insert(NodeID node, Gain gain) { - unsigned address = gain + m_gain_span; + unsigned const address = gain + m_gain_span; if(address > m_max_idx) { - m_max_idx = address; + m_max_idx = address; } - - m_buckets[address].push_back( node ); + + m_buckets[address].push_back( node ); m_queue_index[node].first = m_buckets[address].size() - 1; //store position m_queue_index[node].second = gain; @@ -70,34 +69,34 @@ inline void bucket_pq::insert(NodeID node, Gain gain) { } inline bool bucket_pq::empty( ) { - return m_elements == 0; + return m_elements == 0; } inline Gain bucket_pq::maxValue( ) { - return m_max_idx - m_gain_span; + return m_max_idx - m_gain_span; } inline NodeID bucket_pq::maxElement( ) { - return m_buckets[m_max_idx].back(); + return m_buckets[m_max_idx].back(); } inline NodeID bucket_pq::deleteMax() { - NodeID node = m_buckets[m_max_idx].back(); - m_buckets[m_max_idx].pop_back(); - m_queue_index.erase(node); - - if( m_buckets[m_max_idx].size() == 0 ) { - //update max_idx - while( m_max_idx != 0 ) { - m_max_idx--; - if(m_buckets[m_max_idx].size() > 0) { - break; - } - } - } - - m_elements--; - return node; + NodeID node = m_buckets[m_max_idx].back(); + m_buckets[m_max_idx].pop_back(); + m_queue_index.erase(node); + + if( m_buckets[m_max_idx].size() == 0 ) { + //update max_idx + while( m_max_idx != 0 ) { + m_max_idx--; + if(m_buckets[m_max_idx].size() > 0) { + break; + } + } + } + + m_elements--; + return node; } inline void bucket_pq::decreaseKey(NodeID node, Gain new_gain) { @@ -150,6 +149,6 @@ inline void bucket_pq::deleteNode(NodeID node) { inline bool bucket_pq::contains(NodeID node) { return m_queue_index.find(node) != m_queue_index.end(); } - +} #endif /* end of include guard: BUCKET_PQ_EM8YJPA9 */ diff --git a/parallel/modified_kahip/lib/data_structure/priority_queues/maxNodeHeap.h b/parallel/modified_kahip/lib/data_structure/priority_queues/maxNodeHeap.h index b72b90ae..8f45b91d 100644 --- a/parallel/modified_kahip/lib/data_structure/priority_queues/maxNodeHeap.h +++ b/parallel/modified_kahip/lib/data_structure/priority_queues/maxNodeHeap.h @@ -14,82 +14,79 @@ #include #include "data_structure/priority_queues/priority_queue_interface.h" - -typedef int Key; +namespace kahip::modified { +using Key = int; template < typename Data > class QElement { - public: - QElement( Data data, Key key, int index ) : m_data(data), m_key (key), m_index(index) {}; - virtual ~QElement() {}; +public: + QElement( Data data, Key key, int index ) : m_data(data), m_key (key), m_index(index) {}; - Data & get_data() { - return m_data; - } + Data & get_data() { + return m_data; + } - void set_data(Data & data) { - m_data = data; - } + void set_data(Data & data) { + m_data = data; + } - Key get_key() { - return m_key; - } + Key get_key() { + return m_key; + } - void set_key(Key key) { - m_key = key; - } + void set_key(Key key) { + m_key = key; + } - int get_index() { - return m_index; - } + int get_index() { + return m_index; + } - void set_index(int index) { - m_index = index; - } + void set_index(int index) { + m_index = index; + } - private: - Data m_data; - Key m_key; - int m_index; // the index of the element in the heap +private: + Data m_data; + Key m_key; + int m_index; // the index of the element in the heap }; class maxNodeHeap : public priority_queue_interface { - public: - - struct Data { - NodeID node; - Data( NodeID node ) : node(node) {}; - }; - - typedef QElement PQElement; - - maxNodeHeap() {}; - virtual ~maxNodeHeap() {}; - - NodeID size(); - bool empty(); - - bool contains(NodeID node); - void insert(NodeID id, Gain gain); - - NodeID deleteMax(); - void deleteNode(NodeID node); - NodeID maxElement(); - Gain maxValue(); - - void decreaseKey(NodeID node, Gain gain); - void increaseKey(NodeID node, Gain gain); - void changeKey(NodeID node, Gain gain); - Gain getKey(NodeID node); - - private: - std::vector< PQElement > m_elements; // elements that contain the data - std::unordered_map m_element_index; // stores index of the node in the m_elements array - std::vector< std::pair > m_heap; // key and index in elements (pointer) - - void siftUp( int pos ); - void siftDown( int pos ); +public: + + struct Data { + NodeID node; + Data( NodeID node ) : node(node) {}; + }; + + using PQElement = QElement; + + + NodeID size() override; + bool empty() override; + + bool contains(NodeID node) override; + void insert(NodeID node, Gain gain) override; + + NodeID deleteMax() override; + void deleteNode(NodeID node) override; + NodeID maxElement() override; + Gain maxValue() override; + + void decreaseKey(NodeID node, Gain gain) override; + void increaseKey(NodeID node, Gain gain) override; + void changeKey(NodeID node, Gain gain) override; + Gain getKey(NodeID node) override; + +private: + std::vector< PQElement > m_elements; // elements that contain the data + std::unordered_map m_element_index; // stores index of the node in the m_elements array + std::vector< std::pair > m_heap; // key and index in elements (pointer) + + void siftUp( int pos ); + void siftDown( int pos ); }; @@ -128,7 +125,7 @@ inline void maxNodeHeap::siftDown( int pos ) { siftDown(swap_pos); return; - } + } } else if ( lhsChild < (int)m_heap.size()) { if( m_heap[pos].first < m_heap[lhsChild].first) { @@ -149,31 +146,31 @@ inline void maxNodeHeap::siftDown( int pos ) { } inline void maxNodeHeap::siftUp( int pos ) { - if( pos > 0 ) { - int parentPos = (int)(pos-1)/2; - if( m_heap[parentPos].first < m_heap[pos].first) { - //heap condition not fulfulled - std::swap(m_heap[parentPos], m_heap[pos]); + if( pos > 0 ) { + int parentPos = (int)(pos-1)/2; + if( m_heap[parentPos].first < m_heap[pos].first) { + //heap condition not fulfulled + std::swap(m_heap[parentPos], m_heap[pos]); - int element_pos = m_heap[pos].second; - m_elements[element_pos].set_index(pos); + int element_pos = m_heap[pos].second; + m_elements[element_pos].set_index(pos); - // update the heap index in the element - element_pos = m_heap[parentPos].second; - m_elements[element_pos].set_index(parentPos); + // update the heap index in the element + element_pos = m_heap[parentPos].second; + m_elements[element_pos].set_index(parentPos); - siftUp( parentPos ); - } + siftUp( parentPos ); + } - } + } } inline NodeID maxNodeHeap::size() { - return m_heap.size(); + return m_heap.size(); } inline bool maxNodeHeap::empty( ) { - return m_heap.empty(); + return m_heap.empty(); } inline void maxNodeHeap::insert(NodeID node, Gain gain) { @@ -185,11 +182,11 @@ inline void maxNodeHeap::insert(NodeID node, Gain gain) { m_heap.push_back( std::pair< Key, int>(gain, element_index) ); m_element_index[node] = element_index; siftUp( heap_size ); - } + } } inline void maxNodeHeap::deleteNode(NodeID node) { - int element_index = m_element_index[node]; + int element_index = m_element_index[node]; int heap_index = m_elements[element_index].get_index(); m_element_index.erase(node); @@ -244,13 +241,13 @@ inline NodeID maxNodeHeap::deleteMax() { } return node; - } + } return -1; } inline void maxNodeHeap::changeKey(NodeID node, Gain gain) { - Gain old_gain = m_heap[m_elements[m_element_index[node]].get_index()].first; + Gain old_gain = m_heap[m_elements[m_element_index[node]].get_index()].first; if( old_gain > gain ) { decreaseKey(node, gain); } else if ( old_gain < gain ) { @@ -260,7 +257,7 @@ inline void maxNodeHeap::changeKey(NodeID node, Gain gain) { inline void maxNodeHeap::decreaseKey(NodeID node, Gain gain) { ASSERT_TRUE(m_element_index.find(node) != m_element_index.end()); - int queue_idx = m_element_index[node]; + int queue_idx = m_element_index[node]; int heap_idx = m_elements[queue_idx].get_index(); m_elements[queue_idx].set_key(gain); m_heap[heap_idx].first = gain; @@ -269,7 +266,7 @@ inline void maxNodeHeap::decreaseKey(NodeID node, Gain gain) { inline void maxNodeHeap::increaseKey(NodeID node, Gain gain) { ASSERT_TRUE(m_element_index.find(node) != m_element_index.end()); - int queue_idx = m_element_index[node]; + int queue_idx = m_element_index[node]; int heap_idx = m_elements[queue_idx].get_index(); m_elements[queue_idx].set_key(gain); m_heap[heap_idx].first = gain; @@ -277,12 +274,12 @@ inline void maxNodeHeap::increaseKey(NodeID node, Gain gain) { } inline Gain maxNodeHeap::getKey(NodeID node) { - return m_heap[m_elements[m_element_index[node]].get_index()].first; + return m_heap[m_elements[m_element_index[node]].get_index()].first; }; inline bool maxNodeHeap::contains(NodeID node) { - return m_element_index.find(node) != m_element_index.end(); + return m_element_index.find(node) != m_element_index.end(); +} } - #endif diff --git a/parallel/modified_kahip/lib/data_structure/priority_queues/priority_queue_interface.h b/parallel/modified_kahip/lib/data_structure/priority_queues/priority_queue_interface.h index 63cb8fc7..38c07ff3 100644 --- a/parallel/modified_kahip/lib/data_structure/priority_queues/priority_queue_interface.h +++ b/parallel/modified_kahip/lib/data_structure/priority_queues/priority_queue_interface.h @@ -9,32 +9,37 @@ #define PRIORITY_QUEUE_INTERFACE_20ZSYG7R #include "definitions.h" - +namespace kahip::modified { class priority_queue_interface { - public: - priority_queue_interface( ) {}; - virtual ~priority_queue_interface() {}; - - /* returns the size of the priority queue */ - virtual NodeID size() = 0; - virtual bool empty() = 0 ; - - virtual void insert(NodeID id, Gain gain) = 0; - - virtual Gain maxValue() = 0; - virtual NodeID maxElement() = 0; - virtual NodeID deleteMax() = 0; - - virtual void decreaseKey(NodeID node, Gain newGain) = 0; - virtual void increaseKey(NodeID node, Gain newKey) = 0; - - virtual void changeKey(NodeID element, Gain newKey) = 0; - virtual Gain getKey(NodeID element) = 0; - virtual void deleteNode(NodeID node) = 0; - virtual bool contains(NodeID node) = 0; +public: + priority_queue_interface( ) = default; + virtual ~priority_queue_interface() = default; + // Explicitly default the other special member functions + priority_queue_interface(const priority_queue_interface&) = default; // Copy constructor + priority_queue_interface& operator=(const priority_queue_interface&) = default; // Copy assignment operator + priority_queue_interface(priority_queue_interface&&) = default; // Move constructor + priority_queue_interface& operator=(priority_queue_interface&&) = default; // Move assignment operator + + /* returns the size of the priority queue */ + virtual NodeID size() = 0; + virtual bool empty() = 0 ; + + virtual void insert(NodeID id, Gain gain) = 0; + + virtual Gain maxValue() = 0; + virtual NodeID maxElement() = 0; + virtual NodeID deleteMax() = 0; + + virtual void decreaseKey(NodeID node, Gain newGain) = 0; + virtual void increaseKey(NodeID node, Gain newKey) = 0; + + virtual void changeKey(NodeID element, Gain newKey) = 0; + virtual Gain getKey(NodeID element) = 0; + virtual void deleteNode(NodeID node) = 0; + virtual bool contains(NodeID node) = 0; }; typedef priority_queue_interface refinement_pq; - +} #endif /* end of include guard: PRIORITY_QUEUE_INTERFACE_20ZSYG7R */ diff --git a/parallel/modified_kahip/lib/data_structure/union_find.h b/parallel/modified_kahip/lib/data_structure/union_find.h index 8650f43d..998f2d59 100644 --- a/parallel/modified_kahip/lib/data_structure/union_find.h +++ b/parallel/modified_kahip/lib/data_structure/union_find.h @@ -9,56 +9,56 @@ #define UNION_FIND_H #include - +namespace kahip::modified { // A simple Union-Find datastructure implementation. // This is sometimes also caled "disjoint sets datastructure. class union_find { - public: - union_find(unsigned n) : m_parent(n), m_rank(n), m_n(n) { - for( unsigned i = 0; i < m_parent.size(); i++) { - m_parent[i] = i; - m_rank[i] = 0; - } - }; - inline void Union(unsigned lhs, unsigned rhs) - { - int set_lhs = Find(lhs); - int set_rhs = Find(rhs); - if( set_lhs != set_rhs ) { - if( m_rank[set_lhs] < m_rank[set_rhs]) { - m_parent[set_lhs] = set_rhs; - } else { - m_parent[set_rhs] = set_lhs; - if( m_rank[set_lhs] == m_rank[set_rhs] ) m_rank[set_lhs]++; - } - --m_n; +public: + union_find(unsigned n) : m_parent(n), m_rank(n), m_n(n) { + for( unsigned i = 0; i < m_parent.size(); i++) { + m_parent[i] = i; + m_rank[i] = 0; + } + }; + inline void Union(unsigned lhs, unsigned rhs) + { + int set_lhs = Find(lhs); + int set_rhs = Find(rhs); + if( set_lhs != set_rhs ) { + if( m_rank[set_lhs] < m_rank[set_rhs]) { + m_parent[set_lhs] = set_rhs; + } else { + m_parent[set_rhs] = set_lhs; + if( m_rank[set_lhs] == m_rank[set_rhs] ) m_rank[set_lhs]++; } - }; + --m_n; + } + }; - inline unsigned Find(unsigned element) - { - if( m_parent[element] != element ) { - unsigned retValue = Find( m_parent[element] ); - m_parent[element] = retValue; // path compression - return retValue; - } - return element; - }; + inline unsigned Find(unsigned element) + { + if( m_parent[element] != element ) { + unsigned retValue = Find( m_parent[element] ); + m_parent[element] = retValue; // path compression + return retValue; + } + return element; + }; - // Returns: - // The total number of sets. - inline unsigned n() const - { return m_n; }; + // Returns: + // The total number of sets. + inline unsigned n() const + { return m_n; }; - private: - std::vector< unsigned > m_parent; - std::vector< unsigned > m_rank; +private: + std::vector< unsigned > m_parent; + std::vector< unsigned > m_rank; - // Number of elements in UF data structure. - unsigned m_n; + // Number of elements in UF data structure. + unsigned m_n; }; - +} #endif // ifndef UNION_FIND_H diff --git a/parallel/modified_kahip/lib/definitions.h b/parallel/modified_kahip/lib/definitions.h index 84ead6b5..a4ed893c 100644 --- a/parallel/modified_kahip/lib/definitions.h +++ b/parallel/modified_kahip/lib/definitions.h @@ -15,12 +15,12 @@ #include "limits.h" #include "macros_assertions.h" #include "stdio.h" - +namespace kahip::modified { // allows us to disable most of the output during partitioning #ifdef KAFFPAOUTPUT - #define PRINT(x) x +#define PRINT(x) x #else - #define PRINT(x) do {} while (false); +#define PRINT(x) do {} while (false); #endif /********************************************** @@ -49,94 +49,93 @@ const int ROOT = 0; //for the gpa algorithm struct edge_source_pair { - EdgeID e; - NodeID source; + EdgeID e; + NodeID source; }; struct source_target_pair { - NodeID source; - NodeID target; + NodeID source; + NodeID target; }; //matching array has size (no_of_nodes), so for entry in this table we get the matched neighbor -typedef std::vector CoarseMapping; -typedef std::vector Matching; -typedef std::vector NodePermutationMap; +using CoarseMapping = std::vector; +using Matching = std::vector; +using NodePermutationMap = std::vector; typedef double ImbalanceType; //Coarsening typedef enum { - EXPANSIONSTAR, - EXPANSIONSTAR2, - WEIGHT, - PSEUDOGEOM, - EXPANSIONSTAR2ALGDIST, + EXPANSIONSTAR, + EXPANSIONSTAR2, + WEIGHT, + PSEUDOGEOM, + EXPANSIONSTAR2ALGDIST, } EdgeRating; typedef enum { - PERMUTATION_QUALITY_NONE, - PERMUTATION_QUALITY_FAST, + PERMUTATION_QUALITY_NONE, + PERMUTATION_QUALITY_FAST, PERMUTATION_QUALITY_GOOD } PermutationQuality; typedef enum { - MATCHING_RANDOM, - MATCHING_GPA, + MATCHING_RANDOM, + MATCHING_GPA, MATCHING_RANDOM_GPA, - CLUSTER_COARSENING + CLUSTER_COARSENING } MatchingType; typedef enum { - INITIAL_PARTITIONING_RECPARTITION, + INITIAL_PARTITIONING_RECPARTITION, INITIAL_PARTITIONING_BIPARTITION } InitialPartitioningType; typedef enum { - REFINEMENT_SCHEDULING_FAST, - REFINEMENT_SCHEDULING_ACTIVE_BLOCKS, + REFINEMENT_SCHEDULING_FAST, + REFINEMENT_SCHEDULING_ACTIVE_BLOCKS, REFINEMENT_SCHEDULING_ACTIVE_BLOCKS_REF_KWAY } RefinementSchedulingAlgorithm; typedef enum { - REFINEMENT_TYPE_FM, - REFINEMENT_TYPE_FM_FLOW, + REFINEMENT_TYPE_FM, + REFINEMENT_TYPE_FM_FLOW, REFINEMENT_TYPE_FLOW } RefinementType; typedef enum { - STOP_RULE_SIMPLE, - STOP_RULE_MULTIPLE_K, - STOP_RULE_STRONG + STOP_RULE_SIMPLE, + STOP_RULE_MULTIPLE_K, + STOP_RULE_STRONG } StopRule; typedef enum { - BIPARTITION_BFS, + BIPARTITION_BFS, BIPARTITION_FM } BipartitionAlgorithm ; typedef enum { - KWAY_SIMPLE_STOP_RULE, + KWAY_SIMPLE_STOP_RULE, KWAY_ADAPTIVE_STOP_RULE } KWayStopRule; typedef enum { - COIN_RNDTIE, - COIN_DIFFTIE, - NOCOIN_RNDTIE, - NOCOIN_DIFFTIE + COIN_RNDTIE, + COIN_DIFFTIE, + NOCOIN_RNDTIE, + NOCOIN_DIFFTIE } MLSRule; typedef enum { - CYCLE_REFINEMENT_ALGORITHM_PLAYFIELD, - CYCLE_REFINEMENT_ALGORITHM_ULTRA_MODEL, + CYCLE_REFINEMENT_ALGORITHM_PLAYFIELD, + CYCLE_REFINEMENT_ALGORITHM_ULTRA_MODEL, CYCLE_REFINEMENT_ALGORITHM_ULTRA_MODEL_PLUS } CycleRefinementAlgorithm; typedef enum { - RANDOM_NODEORDERING, - DEGREE_NODEORDERING + RANDOM_NODEORDERING, + DEGREE_NODEORDERING } NodeOrderingType; - - +} #endif diff --git a/parallel/modified_kahip/lib/io/graph_io.cpp b/parallel/modified_kahip/lib/io/graph_io.cpp index 06542f34..1c120491 100644 --- a/parallel/modified_kahip/lib/io/graph_io.cpp +++ b/parallel/modified_kahip/lib/io/graph_io.cpp @@ -7,187 +7,180 @@ #include #include "graph_io.h" - -graph_io::graph_io() { - -} - -graph_io::~graph_io() { - -} +namespace kahip::modified { int graph_io::writeGraphWeighted(graph_access & G, std::string filename) { - std::ofstream f(filename.c_str()); - f << G.number_of_nodes() << " " << G.number_of_edges()/2 << " 11" << std::endl; - - forall_nodes(G, node) { - f << G.getNodeWeight(node) ; - forall_out_edges(G, e, node) { - f << " " << (G.getEdgeTarget(e)+1) << " " << G.getEdgeWeight(e) ; - } endfor - f << std::endl; - } endfor - - f.close(); - return 0; + std::ofstream f(filename.c_str()); + f << G.number_of_nodes() << " " << G.number_of_edges()/2 << " 11" << std::endl; + + forall_nodes(G, node) { + f << G.getNodeWeight(node) ; + forall_out_edges(G, e, node) { + f << " " << (G.getEdgeTarget(e)+1) << " " << G.getEdgeWeight(e) ; + } endfor + f << std::endl; + } endfor + + f.close(); + return 0; } int graph_io::writeGraph(graph_access & G, std::string filename) { - std::ofstream f(filename.c_str()); - f << G.number_of_nodes() << " " << G.number_of_edges()/2 << std::endl; - - forall_nodes(G, node) { - f << node << " "; - forall_out_edges(G, e, node) { - f << G.getEdgeTarget(e) << " " ; - } endfor - f << std::endl; - } endfor - - f.close(); - return 0; + std::ofstream f(filename.c_str()); + f << G.number_of_nodes() << " " << G.number_of_edges()/2 << std::endl; + + forall_nodes(G, node) { + f << node << " "; + forall_out_edges(G, e, node) { + f << G.getEdgeTarget(e) << " " ; + } endfor + f << std::endl; + } endfor + + f.close(); + return 0; } int graph_io::readPartition(graph_access & G, std::string filename) { - std::string line; - - // open file for reading - std::ifstream in(filename.c_str()); - if (!in) { - std::cerr << "Error opening file" << filename << std::endl; - return 1; - } - - PartitionID max = 0; - forall_nodes(G, node) { - // fetch current line - std::getline(in, line); - if (line[0] == '%') { //Comment - node--; - continue; - } - - // in this line we find the block of Node node - G.setPartitionIndex(node, (PartitionID) atol(line.c_str())); - - if(G.getPartitionIndex(node) > max) - max = G.getPartitionIndex(node); - } endfor - - G.set_partition_count(max+1); - in.close(); - - return 0; + std::string line; + + // open file for reading + std::ifstream in(filename.c_str()); + if (!in) { + std::cerr << "Error opening file" << filename << std::endl; + return 1; + } + + PartitionID max = 0; + forall_nodes(G, node) { + // fetch current line + std::getline(in, line); + if (line[0] == '%') { //Comment + node--; + continue; + } + + // in this line we find the block of Node node + G.setPartitionIndex(node, (PartitionID) atol(line.c_str())); + + if(G.getPartitionIndex(node) > max) + max = G.getPartitionIndex(node); + } endfor + + G.set_partition_count(max+1); + in.close(); + + return 0; } int graph_io::readGraphWeighted(graph_access & G, std::string filename) { - std::string line; - - // open file for reading - std::ifstream in(filename.c_str()); - if (!in) { - std::cerr << "Error opening " << filename << std::endl; - return 1; - } - - long nmbNodes; - long nmbEdges; - - std::getline(in,line); - //skip comments - while( line[0] == '%' ) { - std::getline(in, line); - } - - int ew = 0; - std::stringstream ss(line); - ss >> nmbNodes; - ss >> nmbEdges; - ss >> ew; - - if( 2*nmbEdges > std::numeric_limits::max() || nmbNodes > std::numeric_limits::max()) { - std::cout << "The graph is too large. Currently only 32bit supported!" << std::endl; - exit(0); - } - - bool read_ew = false; - bool read_nw = false; - - if(ew == 1) { - read_ew = true; - } else if (ew == 11) { - read_ew = true; - read_nw = true; - } else if (ew == 10) { - read_nw = true; - } - nmbEdges *= 2; //since we have forward and backward edges - - NodeID node_counter = 0; - EdgeID edge_counter = 0; - - G.start_construction(nmbNodes, nmbEdges); - - while( std::getline(in, line)) { - - if (line[0] == '%') { // a comment in the file - continue; - } - - NodeID node = G.new_node(); node_counter++; - G.setPartitionIndex(node, 0); - - std::stringstream ss(line); - - NodeWeight weight = 1; - if( read_nw ) { - ss >> weight; - } - G.setNodeWeight(node, weight); - - NodeID target; - while( ss >> target ) { - EdgeWeight edge_weight = 1; - if( read_ew ) { - ss >> edge_weight; - } - edge_counter++; - EdgeID e = G.new_edge(node, target-1); - G.setEdgeWeight(e, edge_weight); - } - - if(in.eof()) { - break; - } - } - - if( edge_counter != (EdgeID)nmbEdges ) { - std::cout << "number of specified edges mismatch" << std::endl; - std::cout << edge_counter << " " << nmbEdges << std::endl; - exit(0); - } - - if( node_counter != (NodeID)nmbNodes) { - std::cout << "number of specified nodes mismatch" << std::endl; - std::cout << node_counter << " " << nmbNodes << std::endl; - exit(0); - } - - - G.finish_construction(); - return 0; + std::string line; + + // open file for reading + std::ifstream in(filename.c_str()); + if (!in) { + std::cerr << "Error opening " << filename << std::endl; + return 1; + } + + long nmbNodes; + long nmbEdges; + + std::getline(in,line); + //skip comments + while( line[0] == '%' ) { + std::getline(in, line); + } + + int ew = 0; + std::stringstream ss(line); + ss >> nmbNodes; + ss >> nmbEdges; + ss >> ew; + + if( 2*nmbEdges > std::numeric_limits::max() || nmbNodes > std::numeric_limits::max()) { + std::cout << "The graph is too large. Currently only 32bit supported!" << std::endl; + exit(0); + } + + bool read_ew = false; + bool read_nw = false; + + if(ew == 1) { + read_ew = true; + } else if (ew == 11) { + read_ew = true; + read_nw = true; + } else if (ew == 10) { + read_nw = true; + } + nmbEdges *= 2; //since we have forward and backward edges + + NodeID node_counter = 0; + EdgeID edge_counter = 0; + + G.start_construction(nmbNodes, nmbEdges); + + while( std::getline(in, line)) { + + if (line[0] == '%') { // a comment in the file + continue; + } + + NodeID node = G.new_node(); node_counter++; + G.setPartitionIndex(node, 0); + + std::stringstream ss(line); + + NodeWeight weight = 1; + if( read_nw ) { + ss >> weight; + } + G.setNodeWeight(node, weight); + + NodeID target; + while( ss >> target ) { + EdgeWeight edge_weight = 1; + if( read_ew ) { + ss >> edge_weight; + } + edge_counter++; + EdgeID e = G.new_edge(node, target-1); + G.setEdgeWeight(e, edge_weight); + } + + if(in.eof()) { + break; + } + } + + if( edge_counter != (EdgeID)nmbEdges ) { + std::cout << "number of specified edges mismatch" << std::endl; + std::cout << edge_counter << " " << nmbEdges << std::endl; + exit(0); + } + + if( node_counter != (NodeID)nmbNodes) { + std::cout << "number of specified nodes mismatch" << std::endl; + std::cout << node_counter << " " << nmbNodes << std::endl; + exit(0); + } + + + G.finish_construction(); + return 0; } void graph_io::writePartition(graph_access & G, std::string filename) { - std::ofstream f(filename.c_str()); - std::cout << "writing partition to " << filename << " ... " << std::endl; + std::ofstream f(filename.c_str()); + std::cout << "writing partition to " << filename << " ... " << std::endl; - forall_nodes(G, node) { - f << G.getPartitionIndex(node) << std::endl; - } endfor + forall_nodes(G, node) { + f << G.getPartitionIndex(node) << std::endl; + } endfor - f.close(); + f.close(); +} } - diff --git a/parallel/modified_kahip/lib/io/graph_io.h b/parallel/modified_kahip/lib/io/graph_io.h index 45c452d0..59cb5576 100644 --- a/parallel/modified_kahip/lib/io/graph_io.h +++ b/parallel/modified_kahip/lib/io/graph_io.h @@ -18,32 +18,38 @@ #include "definitions.h" #include "data_structure/graph_access.h" - +namespace kahip::modified { class graph_io { - public: - graph_io(); - virtual ~graph_io () ; +public: + graph_io() = default; // Default constructor + virtual ~graph_io() = default; // Default destructor + + // Explicitly default the other special member functions + graph_io(const graph_io&) = default; // Copy constructor + graph_io& operator=(const graph_io&) = default; // Copy assignment operator + graph_io(graph_io&&) = default; // Move constructor + graph_io& operator=(graph_io&&) = default; // Move assignment operator - static - int readGraphWeighted(graph_access & G, std::string filename); + static + int readGraphWeighted(graph_access & G, std::string filename); - static - int writeGraphWeighted(graph_access & G, std::string filename); + static + int writeGraphWeighted(graph_access & G, std::string filename); - static - int writeGraph(graph_access & G, std::string filename); + static + int writeGraph(graph_access & G, std::string filename); - static - int readPartition(graph_access& G, std::string filename); + static + int readPartition(graph_access& G, std::string filename); - static - void writePartition(graph_access& G, std::string filename); + static + void writePartition(graph_access& G, std::string filename); - template - static void writeVector(std::vector & vec, std::string filename); + template + static void writeVector(std::vector & vec, std::string filename); - template - static void readVector(std::vector & vec, std::string filename); + template + static void readVector(std::vector & vec, std::string filename); }; @@ -84,5 +90,5 @@ void graph_io::readVector(std::vector & vec, std::string filename) { in.close(); } - +} #endif /*GRAPHIO_H_*/ diff --git a/parallel/modified_kahip/lib/parallel_mh/diversifyer.h b/parallel/modified_kahip/lib/parallel_mh/diversifyer.h index e0667cdd..d84fd3af 100644 --- a/parallel/modified_kahip/lib/parallel_mh/diversifyer.h +++ b/parallel/modified_kahip/lib/parallel_mh/diversifyer.h @@ -9,11 +9,17 @@ #define DIVERSIFYER_AZQIF42R #include "random_functions.h" - +namespace kahip::modified { class diversifyer { public: - diversifyer() {} ; - virtual ~diversifyer() {}; + diversifyer() = default; // Default constructor + virtual ~diversifyer() = default; // Default destructor + + // Explicitly default the other special member functions + diversifyer(const diversifyer&) = default; // Copy constructor + diversifyer& operator=(const diversifyer&) = default; // Copy assignment operator + diversifyer(diversifyer&&) = default; // Move constructor + diversifyer& operator=(diversifyer&&) = default; // Move assignment operator void diversify(PartitionConfig & config) { //diversify edge rating: @@ -29,6 +35,6 @@ class diversifyer { config.kaba_unsucc_iterations = random_functions::nextInt(1, 10); } }; - +} #endif /* end of include guard: DIVERSIFYER_AZQIF42R */ diff --git a/parallel/modified_kahip/lib/parallel_mh/exchange/exchanger.cpp b/parallel/modified_kahip/lib/parallel_mh/exchange/exchanger.cpp index 423733c3..81fae8ef 100644 --- a/parallel/modified_kahip/lib/parallel_mh/exchange/exchanger.cpp +++ b/parallel/modified_kahip/lib/parallel_mh/exchange/exchanger.cpp @@ -1,294 +1,441 @@ /****************************************************************************** * exchanger.cpp - * * + * * Source of KaHIP -- Karlsruhe High Quality Partitioning. * Christian Schulz *****************************************************************************/ -#include - #include "exchanger.h" -#include "tools/quality_metrics.h" -#include "tools/random_functions.h" - -exchanger::exchanger(MPI_Comm communicator) { - m_prev_best_objective = std::numeric_limits::max(); - - m_communicator = communicator; - - int rank, comm_size; - MPI_Comm_rank( m_communicator, &rank); - MPI_Comm_size( m_communicator, &comm_size); - - m_cur_num_pushes = 0; - if(comm_size > 2) m_max_num_pushes = ceil(log2(comm_size)); - else m_max_num_pushes = 1; - std::cout << "max num pushes " << m_max_num_pushes << std::endl; +#include - m_allready_send_to.resize(comm_size); +#include +#include +#include +#include +#include +#include +#include - for( unsigned i = 0; i < m_allready_send_to.size(); i++) { - m_allready_send_to[i] = false; - } +#include "parallel_mh/evolutionary_collectives.h" +#include "parallel_mh/population_size_broadcast.h" +#include "tools/quality_metrics.h" +#include "tools/random_functions.h" - m_allready_send_to[rank] = true; +namespace kahip::modified { +auto exchanger::pending_send::operator=(pending_send&& other) noexcept + -> pending_send& { + if (this != &other) { + if (request != MPI_REQUEST_NULL) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + communicator, "evolutionary pending-send move assignment", + "live MPI request would lose its completion ownership"); + } + payload = std::move(other.payload); + request = std::exchange(other.request, MPI_REQUEST_NULL); + communicator = std::exchange(other.communicator, MPI_COMM_NULL); + } + return *this; } -exchanger::~exchanger() { - MPI_Barrier( m_communicator ); - int rank; - MPI_Comm_rank( m_communicator, &rank); - - int flag; MPI_Status st; - MPI_Iprobe(MPI_ANY_SOURCE, MPI_ANY_TAG, m_communicator, &flag, &st); - - while(flag) { - int message_length; - MPI_Get_count(&st, MPI_INT, &message_length); - - int* partition_map = new int[message_length]; - MPI_Status rst; - MPI_Recv( partition_map, message_length, MPI_INT, st.MPI_SOURCE, rank, m_communicator, &rst); - - delete[] partition_map; - MPI_Iprobe(MPI_ANY_SOURCE, MPI_ANY_TAG, m_communicator, &flag, &st); - } - - MPI_Barrier( m_communicator ); - for( unsigned i = 0; i < m_request_pointers.size(); i++) { - MPI_Cancel( m_request_pointers[i] ); - } - - for( unsigned i = 0; i < m_request_pointers.size(); i++) { - MPI_Status st; - MPI_Wait( m_request_pointers[i], & st ); - delete[] m_partition_map_buffers[i]; - delete m_request_pointers[i]; - } - +exchanger::exchanger(MPI_Comm communicator) + : m_prev_best_objective(std::numeric_limits::max()), + m_max_num_pushes(1), + m_rank(-1), + m_size(0), + m_communicator(communicator) { + if (!::kahip::parallel_mh::detail::mpi_runtime_is_active()) { + ::kahip::parallel_mh::detail::abort_evolutionary_lifecycle( + "evolutionary exchange requires an active MPI runtime"); + } + if (m_communicator == MPI_COMM_NULL) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + MPI_COMM_WORLD, "evolutionary exchange construction", + "exchange requires a live intracommunicator"); + } + + auto is_intercommunicator = 0; + ::kahip::parallel_mh::detail::check_mpi( + MPI_Comm_test_inter(m_communicator, &is_intercommunicator), + m_communicator, "MPI_Comm_test_inter(evolutionary exchange)"); + if (is_intercommunicator != 0) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, "evolutionary exchange construction", + "exchange requires an intracommunicator"); + } + + ::kahip::parallel_mh::detail::check_mpi( + MPI_Comm_rank(m_communicator, &m_rank), m_communicator, + "MPI_Comm_rank(evolutionary exchange)"); + ::kahip::parallel_mh::detail::check_mpi( + MPI_Comm_size(m_communicator, &m_size), m_communicator, + "MPI_Comm_size(evolutionary exchange)"); + if (m_rank < 0 || m_rank >= m_size || m_size <= 0) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, "evolutionary exchange construction", + "MPI returned an invalid evolutionary communicator rank or size"); + } + + m_max_num_pushes = + m_size > 2 ? static_cast(std::ceil(std::log2(m_size))) : 1; + std::cout << "max num pushes " << m_max_num_pushes << std::endl; + m_already_sent_to.assign(static_cast(m_size), false); + m_already_sent_to[static_cast(m_rank)] = true; + m_issued_sends.assign(static_cast(m_size), 0); + m_consumed_receives.assign(static_cast(m_size), 0); } -void exchanger::diversify_population( PartitionConfig & config, graph_access & G, population & island, bool replace ) { - - int rank, comm_size; - MPI_Comm_rank( m_communicator, &rank); - MPI_Comm_size( m_communicator, &comm_size); - - std::vector permutation(comm_size, 0); - - if( rank == ROOT ) { - random_functions::circular_permutation(permutation); - } - - MPI_Bcast(&permutation[0], comm_size, MPI_INT, ROOT, m_communicator); - - int from = 0; - int to = permutation[rank]; - for( unsigned i = 0; i < permutation.size(); i++) { - if( permutation[i] == (unsigned)rank ) { - from = (int)i; - break; - } - } - - Individuum in; - Individuum out; - - if(config.mh_diversify_best) { - island.get_best_individuum(in); - } else { - island.get_random_individuum(in); - } - exchange_individum( config, G, from, rank, to, in, out); - - if( replace ) { - island.replace( in, out ); - } else { - island.insert( G, out ); - } - +exchanger::~exchanger() noexcept { + if (!m_finished) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, "evolutionary rumor exchange teardown", + "exchanger destroyed before explicit finish drained all messages"); + } } -void exchanger::quick_start( PartitionConfig & config, graph_access & G, population & island ) { - int comm_size; - MPI_Comm_size( m_communicator, &comm_size); - - unsigned no_of_individuals = ceil(config.mh_pool_size / (double)comm_size) - 1; - - std::cout << "creating " << no_of_individuals << std::endl; - - for(unsigned i = 0; i < no_of_individuals; i++) { - PartitionConfig copy = config; - copy.combine = false; - copy.graph_allready_partitioned = false; - - Individuum ind; - island.createIndividuum(config, G, ind, true); - island.insert(G, ind); - } - - int reps = config.mh_pool_size - no_of_individuals; - if(reps < 0) reps = 0; - - PartitionConfig div_config = config; - div_config.mh_diversify_best = false; - for( unsigned i = 0; i < (unsigned) reps; i++) { - diversify_population( div_config , G, island, false); - } +auto exchanger::observe_graph_order(std::size_t graph_order, + std::string_view operation) -> int { + if (m_finished) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, operation, + "evolutionary exchange used after explicit finish"); + } + if (m_graph_order_observed && graph_order != m_graph_order) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, operation, + "evolutionary exchange graph order changed during its lifetime"); + } + m_graph_order = graph_order; + m_graph_order_observed = true; + return ::kahip::parallel_mh::detail::checked_count(graph_order, + m_communicator, operation); } +void exchanger::validate_partition_status(MPI_Status const& status, + int expected_source, + int expected_tag, + int expected_count, + std::string_view operation) const { + if (status.MPI_SOURCE != expected_source || expected_source < 0 || + expected_source >= m_size) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, operation, + "rumor message source does not match the requested peer"); + } + if (status.MPI_TAG != expected_tag) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, operation, + "rumor message tag does not match receiver rank"); + } + auto received_count = 0; + ::kahip::parallel_mh::detail::check_mpi( + MPI_Get_count(&status, MPI_INT, &received_count), m_communicator, + "MPI_Get_count(evolutionary partition payload)"); + if (received_count != expected_count) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, operation, + "rumor message count does not match the graph order"); + } +} -void exchanger::exchange_individum( const PartitionConfig & config, graph_access & G, - int & from, int & rank, int & to, - Individuum & in, Individuum & out) { - //recv. edge cut, partition_map, cut_edges from "from" - //send in to "to" - - int* partition_map = new int[G.number_of_nodes()]; - out.partition_map = partition_map; - out.cut_edges = new std::vector(); +void exchanger::diversify_population(PartitionConfig& config, + graph_access& graph, + population& island, + bool replace) { + static_cast( + observe_graph_order(static_cast(graph.number_of_nodes()), + "MPI_Sendrecv(evolutionary permutation exchange)")); + auto permutation = std::vector(static_cast(m_size), 0); + if (m_rank == ROOT) { + random_functions::circular_permutation(permutation); + } + ::kahip::parallel_mh::broadcast_permutation(m_communicator, permutation, + ROOT); + + auto canonical = permutation; + std::ranges::sort(canonical); + auto expected = std::vector(canonical.size()); + std::iota(expected.begin(), expected.end(), 0U); + if (canonical != expected) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, "MPI_Bcast(evolutionary permutation)", + "evolutionary permutation is not a bijection of communicator ranks"); + } + + auto const destination = + static_cast(permutation[static_cast(m_rank)]); + auto const source_position = + std::ranges::find(permutation, static_cast(m_rank)); + auto const source = + static_cast(std::distance(permutation.begin(), source_position)); + + auto input = Individuum{}; + auto output = Individuum{}; + if (config.mh_diversify_best) { + island.get_best_individuum(input); + } else { + island.get_random_individuum(input); + } + exchange_individum(config, graph, source, destination, input, output); + if (replace) { + island.replace(input, output); + } else { + island.insert(graph, output); + } +} - MPI_Status st; - MPI_Sendrecv( in.partition_map , G.number_of_nodes(), MPI_INT, to, 0, - out.partition_map, G.number_of_nodes(), MPI_INT, from, 0, m_communicator, &st); +void exchanger::quick_start(PartitionConfig& config, + graph_access& graph, + population& island) { + static_cast( + observe_graph_order(static_cast(graph.number_of_nodes()), + "evolutionary quick-start")); + auto const plan = ::kahip::parallel_mh::quick_start_population_plan( + config.mh_pool_size, m_size); + if (!plan.has_value()) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, "evolutionary quick-start", + "quick-start requires a positive communicator size"); + } + std::cout << "creating " << plan->local_creations << std::endl; + for (auto index = 0U; index < plan->local_creations; ++index) { + auto individual = Individuum{}; + island.createIndividuum(config, graph, individual, true); + island.insert(graph, individual); + } + + auto diversify_config = config; + diversify_config.mh_diversify_best = false; + for (auto index = 0U; index < plan->diversifications; ++index) { + diversify_population(diversify_config, graph, island, false); + } +} - //recompute cut edges and edge cut locally - forall_nodes(G, node) { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if(partition_map[node] != partition_map[target]) { - out.cut_edges->push_back(e); - } - } endfor - } endfor +void exchanger::exchange_individum(PartitionConfig const& config, + graph_access& graph, + int source, + int destination, + Individuum& input, + Individuum& output) { + auto const graph_count = + observe_graph_order(static_cast(graph.number_of_nodes()), + "MPI_Sendrecv(evolutionary permutation exchange)"); + if (source < 0 || source >= m_size || destination < 0 || + destination >= m_size || input.partition_map == nullptr) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, "MPI_Sendrecv(evolutionary permutation exchange)", + "permutation exchange arguments are invalid"); + } + + auto partition_map = std::make_unique( + static_cast(graph.number_of_nodes())); + auto cut_edges = std::make_unique>(); + auto status = MPI_Status{}; + ::kahip::parallel_mh::detail::check_mpi( + MPI_Sendrecv(input.partition_map, graph_count, MPI_INT, destination, 0, + partition_map.get(), graph_count, MPI_INT, source, 0, + m_communicator, &status), + m_communicator, "MPI_Sendrecv(evolutionary permutation exchange)"); + validate_partition_status(status, source, 0, graph_count, + "MPI_Sendrecv(evolutionary permutation exchange)"); + + forall_nodes(graph, node){forall_out_edges( + graph, edge, node){auto const target = graph.getEdgeTarget(edge); + if (partition_map[node] != partition_map[target]) { + cut_edges->push_back(edge); + } +} +endfor +} // namespace kahip::modified +endfor output.objective = m_qm.objective(config, graph, partition_map.get()); +output.partition_map = partition_map.release(); +output.cut_edges = cut_edges.release(); +} - out.objective = m_qm.objective(config, G, partition_map); +void exchanger::push_best(PartitionConfig& config, + graph_access& graph, + population& island) { + static_cast(config); + auto const graph_count = + observe_graph_order(static_cast(graph.number_of_nodes()), + "MPI_Isend(evolutionary rumor)"); + auto best = Individuum{}; + island.get_best_individuum(best); + if (::kahip::parallel_mh::objective_improved(best.objective, + m_prev_best_objective)) { + m_prev_best_objective = best.objective; + std::ranges::fill(m_already_sent_to, false); + m_already_sent_to[static_cast(m_rank)] = true; + m_cur_num_pushes = 0; + std::cout << "rank " << m_rank + << ": pool improved *************************************** " + << best.objective << std::endl; + } + + auto something_to_do = + std::ranges::any_of(m_already_sent_to, [](bool sent) { return !sent; }); + if (m_cur_num_pushes > m_max_num_pushes) + something_to_do = false; + if (something_to_do) { + auto payload = + std::vector(static_cast(graph.number_of_nodes())); + forall_nodes(graph, node) { + payload[static_cast(node)] = graph.getPartitionIndex(node); + } + endfor + + auto target = m_rank; + // Retain the paper's asynchronous rumor selection and exact draw order. + while (m_already_sent_to[static_cast(target)]) { + target = random_functions::nextInt(0, m_size - 1); + } + auto& issued = m_issued_sends[static_cast(target)]; + if (issued == std::numeric_limits::max()) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, "MPI_Isend(evolutionary rumor)", + "evolutionary rumor send count exceeds uint64_t"); + } + m_pending_sends.emplace_back(std::move(payload), MPI_REQUEST_NULL, + m_communicator); + auto& pending = m_pending_sends.back(); + ::kahip::parallel_mh::detail::check_mpi( + MPI_Isend(pending.payload.data(), graph_count, MPI_INT, target, target, + m_communicator, &pending.request), + m_communicator, "MPI_Isend(evolutionary rumor)"); + ++issued; + ++m_cur_num_pushes; + m_already_sent_to[static_cast(target)] = true; + } + retire_completed_sends(); } +void exchanger::retire_completed_sends() { + std::erase_if(m_pending_sends, [&](pending_send& pending) { + auto complete = 0; + ::kahip::parallel_mh::detail::check_mpi( + MPI_Test(&pending.request, &complete, MPI_STATUS_IGNORE), + m_communicator, "MPI_Test(evolutionary rumor)"); + return complete != 0; + }); +} -//extended push protocol -- see paper for details -void exchanger::push_best( PartitionConfig & config, graph_access & G, population & island ) { - int rank, size; - MPI_Comm_rank( m_communicator, &rank); - MPI_Comm_size( m_communicator, &size); - - Individuum best_ind; - island.get_best_individuum(best_ind); - - if( best_ind.objective < m_prev_best_objective) { - m_prev_best_objective = best_ind.objective; - for( unsigned i = 0; i < m_allready_send_to.size(); i++) { - m_allready_send_to[i] = false; - } - - m_allready_send_to[rank] = true; - m_cur_num_pushes = 0; - - std::cout << "rank " << rank - << ": pool improved *************************************** " - << best_ind.objective << std::endl; - } - - bool something_todo = false; - for( unsigned i = 0; i < m_allready_send_to.size(); i++) { - if(!m_allready_send_to[i]) { - something_todo = true; - break; - } - } - - if( m_cur_num_pushes > m_max_num_pushes ) { - something_todo = false; - } - - if(something_todo) { - int* partition_map = new int[G.number_of_nodes()]; - forall_nodes(G, node) { - partition_map[node] = G.getPartitionIndex(node); - } endfor - - int target = rank; - while( target == rank && m_allready_send_to[target]) target = random_functions::nextInt(0, size-1); - - //MPI::Request* request = new MPI::Request; - //*request = MPI::COMM_WORLD.Isend( partition_map, G.number_of_nodes(), MPI_INT, target, target); - MPI_Request* rq = new MPI_Request; - MPI_Isend( partition_map, G.number_of_nodes(), MPI_INT, target, target, m_communicator, rq); - - - m_cur_num_pushes++; - - m_request_pointers.push_back( rq ); - m_partition_map_buffers.push_back( partition_map ); - - m_allready_send_to[target] = true; - } - - for( unsigned i = 0; i < m_request_pointers.size(); i++) { - int finished = 0; - MPI_Status st; - MPI_Test( m_request_pointers[i], &finished, &st); - - if(finished) { - std::swap(m_request_pointers[i], m_request_pointers[m_request_pointers.size()-1]); - std::swap(m_partition_map_buffers[i], m_partition_map_buffers[m_request_pointers.size()-1]); - - delete[] m_partition_map_buffers[m_partition_map_buffers.size() - 1]; - delete m_request_pointers[m_request_pointers.size() - 1]; - - m_partition_map_buffers.pop_back(); - m_request_pointers.pop_back(); - } - } +void exchanger::receive_available(PartitionConfig& config, + graph_access& graph, + population& island, + MPI_Status const& probe_status, + int graph_count) { + validate_partition_status(probe_status, probe_status.MPI_SOURCE, m_rank, + graph_count, "MPI_Recv(evolutionary rumor)"); + auto const source = probe_status.MPI_SOURCE; + auto partition_map = std::make_unique( + static_cast(graph.number_of_nodes())); + auto cut_edges = std::make_unique>(); + auto receive_status = MPI_Status{}; + ::kahip::parallel_mh::detail::check_mpi( + MPI_Recv(partition_map.get(), graph_count, MPI_INT, source, m_rank, + m_communicator, &receive_status), + m_communicator, "MPI_Recv(evolutionary rumor)"); + validate_partition_status(receive_status, source, m_rank, graph_count, + "MPI_Recv(evolutionary rumor)"); + + forall_nodes(graph, node){forall_out_edges( + graph, edge, node){auto const target = graph.getEdgeTarget(edge); + if (partition_map[node] != partition_map[target]) { + cut_edges->push_back(edge); + } +} +endfor +} +endfor auto output = Individuum{}; +output.objective = m_qm.objective(config, graph, partition_map.get()); +output.partition_map = partition_map.release(); +output.cut_edges = cut_edges.release(); +island.insert(graph, output); + +auto& consumed = m_consumed_receives[static_cast(source)]; +if (consumed == std::numeric_limits::max()) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, "MPI_Recv(evolutionary rumor)", + "evolutionary rumor receive count exceeds uint64_t"); +} +++consumed; +if (::kahip::parallel_mh::objective_improved(output.objective, + m_prev_best_objective)) { + m_prev_best_objective = output.objective; + std::cout << "rank " << m_rank + << ": pool improved (inc) " + "**************************************** " + << output.objective << std::endl; + std::ranges::fill(m_already_sent_to, false); + m_already_sent_to[static_cast(m_rank)] = true; + m_cur_num_pushes = 0; +} +m_already_sent_to[static_cast(source)] = true; } -void exchanger::recv_incoming( PartitionConfig & config, graph_access & G, population & island ) { - int rank; - MPI_Comm_rank( m_communicator, &rank); - - int flag; MPI_Status st; - MPI_Iprobe(MPI_ANY_SOURCE, MPI_ANY_TAG, m_communicator, &flag, &st); - - while(flag) { - Individuum out; - int* partition_map = new int[G.number_of_nodes()]; - out.partition_map = partition_map; - out.cut_edges = new std::vector(); - - MPI_Status rst; - MPI_Recv( out.partition_map, G.number_of_nodes(), MPI_INT, st.MPI_SOURCE, rank, m_communicator, &rst); - - //recompute cut edges and edge cut locally - forall_nodes(G, node) { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if(partition_map[node] != partition_map[target]) { - out.cut_edges->push_back(e); - } - } endfor - } endfor - - out.objective = m_qm.objective(config, G, partition_map); - island.insert( G, out ); - - if( (unsigned)out.objective < (unsigned)m_prev_best_objective) { - m_prev_best_objective = out.objective; - std::cout << "rank " << rank - << ": pool improved (inc) **************************************** " - << out.objective << std::endl; - - for( unsigned i = 0; i < m_allready_send_to.size(); i++) { - m_allready_send_to[i] = false; - } - - m_allready_send_to[rank] = true; - m_cur_num_pushes = 0; - } - - m_allready_send_to[st.MPI_SOURCE] = true; // we dont need to send it back - saves us P * 1 messages of length n - - MPI_Iprobe(MPI_ANY_SOURCE, MPI_ANY_TAG, m_communicator, &flag, &st); - } +void exchanger::recv_incoming(PartitionConfig& config, + graph_access& graph, + population& island) { + auto const graph_count = + observe_graph_order(static_cast(graph.number_of_nodes()), + "MPI_Recv(evolutionary rumor)"); + auto available = 0; + auto probe_status = MPI_Status{}; + ::kahip::parallel_mh::detail::check_mpi( + MPI_Iprobe(MPI_ANY_SOURCE, MPI_ANY_TAG, m_communicator, &available, + &probe_status), + m_communicator, "MPI_Iprobe(evolutionary rumor)"); + while (available != 0) { + receive_available(config, graph, island, probe_status, graph_count); + ::kahip::parallel_mh::detail::check_mpi( + MPI_Iprobe(MPI_ANY_SOURCE, MPI_ANY_TAG, m_communicator, &available, + &probe_status), + m_communicator, "MPI_Iprobe(evolutionary rumor)"); + } } +void exchanger::finish(std::size_t graph_order) { + auto const graph_count = + observe_graph_order(graph_order, "evolutionary rumor exchange finish"); + auto incoming = std::vector(static_cast(m_size)); + ::kahip::parallel_mh::detail::check_mpi( + MPI_Alltoall(m_issued_sends.data(), 1, MPI_UINT64_T, incoming.data(), 1, + MPI_UINT64_T, m_communicator), + m_communicator, "MPI_Alltoall(evolutionary rumor counts)"); + + for (auto source = 0; source < m_size; ++source) { + auto& consumed = m_consumed_receives[static_cast(source)]; + auto const expected = incoming[static_cast(source)]; + if (consumed > expected) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, "evolutionary rumor exchange finish", + "consumed rumor count exceeds the sender's issued count"); + } + while (consumed < expected) { + auto probe_status = MPI_Status{}; + ::kahip::parallel_mh::detail::check_mpi( + MPI_Probe(source, MPI_ANY_TAG, m_communicator, &probe_status), + m_communicator, "MPI_Probe(evolutionary rumor drain)"); + validate_partition_status(probe_status, source, m_rank, graph_count, + "MPI_Recv(evolutionary rumor drain)"); + auto payload = std::vector(graph_order); + auto receive_status = MPI_Status{}; + ::kahip::parallel_mh::detail::check_mpi( + MPI_Recv(payload.data(), graph_count, MPI_INT, source, m_rank, + m_communicator, &receive_status), + m_communicator, "MPI_Recv(evolutionary rumor drain)"); + validate_partition_status(receive_status, source, m_rank, graph_count, + "MPI_Recv(evolutionary rumor drain)"); + ++consumed; + } + } + + for (auto& pending : m_pending_sends) { + ::kahip::parallel_mh::detail::check_mpi( + MPI_Wait(&pending.request, MPI_STATUS_IGNORE), m_communicator, + "MPI_Wait(evolutionary rumor)"); + } + m_pending_sends.clear(); + m_finished = true; +} +} // namespace kahip::modified diff --git a/parallel/modified_kahip/lib/parallel_mh/exchange/exchanger.h b/parallel/modified_kahip/lib/parallel_mh/exchange/exchanger.h index 202fad79..b2ecaa11 100644 --- a/parallel/modified_kahip/lib/parallel_mh/exchange/exchanger.h +++ b/parallel/modified_kahip/lib/parallel_mh/exchange/exchanger.h @@ -10,42 +10,101 @@ #include +#include +#include +#include +#include +#include + #include "data_structure/graph_access.h" #include "parallel_mh/population.h" #include "partition_config.h" #include "tools/quality_metrics.h" +namespace kahip::modified { +class exchanger final { + public: + explicit exchanger(MPI_Comm communicator); + ~exchanger() noexcept; -class exchanger { -public: - exchanger( MPI_Comm communicator ); - virtual ~exchanger(); + exchanger(exchanger const&) = delete; + auto operator=(exchanger const&) -> exchanger& = delete; + exchanger(exchanger&&) = delete; + auto operator=(exchanger&&) -> exchanger& = delete; - void diversify_population( PartitionConfig & config, graph_access & G, population & island, bool replace ); - void quick_start( PartitionConfig & config, graph_access & G, population & island ); - void push_best( PartitionConfig & config, graph_access & G, population & island ); - void recv_incoming( PartitionConfig & config, graph_access & G, population & island ); + void diversify_population(PartitionConfig& config, + graph_access& graph, + population& island, + bool replace); + void quick_start(PartitionConfig& config, + graph_access& graph, + population& island); + void push_best(PartitionConfig& config, + graph_access& graph, + population& island); + void recv_incoming(PartitionConfig& config, + graph_access& graph, + population& island); + void finish(std::size_t graph_order); -private: - void exchange_individum(const PartitionConfig & config, - graph_access & G, - int & from, - int & rank, - int & to, - Individuum & in, Individuum & out); + private: + struct pending_send final { + std::vector payload; + MPI_Request request = MPI_REQUEST_NULL; + MPI_Comm communicator = MPI_COMM_NULL; - std::vector< int* > m_partition_map_buffers; - std::vector< MPI_Request* > m_request_pointers; - std::vector m_allready_send_to; + pending_send(std::vector values, + MPI_Request handle, + MPI_Comm failure_communicator) noexcept + : payload(std::move(values)), + request(handle), + communicator(failure_communicator) {} + pending_send(pending_send const&) = delete; + auto operator=(pending_send const&) -> pending_send& = delete; + pending_send(pending_send&& other) noexcept + : payload(std::move(other.payload)), + request(std::exchange(other.request, MPI_REQUEST_NULL)), + communicator(std::exchange(other.communicator, MPI_COMM_NULL)) {} + auto operator=(pending_send&& other) noexcept -> pending_send&; + }; - int m_prev_best_objective; - int m_max_num_pushes; - int m_cur_num_pushes; + void exchange_individum(PartitionConfig const& config, + graph_access& graph, + int source, + int destination, + Individuum& input, + Individuum& output); + [[nodiscard]] auto observe_graph_order(std::size_t graph_order, + std::string_view operation) -> int; + void validate_partition_status(MPI_Status const& status, + int expected_source, + int expected_tag, + int expected_count, + std::string_view operation) const; + void receive_available(PartitionConfig& config, + graph_access& graph, + population& island, + MPI_Status const& probe_status, + int graph_count); + void retire_completed_sends(); - MPI_Comm m_communicator; + std::vector m_pending_sends; + std::vector m_already_sent_to; + std::vector m_issued_sends; + std::vector m_consumed_receives; - quality_metrics m_qm; -}; + EdgeWeight m_prev_best_objective; + int m_max_num_pushes; + int m_cur_num_pushes = 0; + int m_rank; + int m_size; + std::size_t m_graph_order = 0; + bool m_graph_order_observed = false; + bool m_finished = false; + MPI_Comm m_communicator; + quality_metrics m_qm; +}; +} // namespace kahip::modified #endif /* end of include guard: EXCHANGER_YPB6QKNL */ diff --git a/parallel/modified_kahip/lib/parallel_mh/galinier_combine/construct_partition.cpp b/parallel/modified_kahip/lib/parallel_mh/galinier_combine/construct_partition.cpp index 8b117cab..228e3966 100644 --- a/parallel/modified_kahip/lib/parallel_mh/galinier_combine/construct_partition.cpp +++ b/parallel/modified_kahip/lib/parallel_mh/galinier_combine/construct_partition.cpp @@ -15,138 +15,132 @@ #include "uncoarsening/refinement/refinement.h" #include "uncoarsening/refinement/tabu_search/tabu_search.h" - -construct_partition::construct_partition() { - -} - -construct_partition::~construct_partition() { - -} +namespace kahip::modified { void construct_partition::construct_starting_from_partition( PartitionConfig & config, graph_access & G) { - std::vector< std::queue< NodeID > > queues(config.k); - std::vector< NodeID > unassigned_vertices; - std::vector< std::vector< NodeID > > blocks(config.k); - - forall_nodes(G, node) { - if( G.getPartitionIndex(node) == config.k ) { - unassigned_vertices.push_back(node); - } else { - blocks[G.getPartitionIndex(node)].push_back(node); - } - } endfor - - - //shuffle the blocks - std::vector< NodeWeight > block_weights(config.k, 0); - for( unsigned block = 0; block < config.k; block++) { - random_functions::permutate_vector_good(blocks[block], false); - block_weights[block] = blocks[block].size(); - } - - for( unsigned block = 0; block < config.k; block++) { - for( unsigned j = 0; j < blocks[block].size(); j++) { - NodeID node = blocks[block][j]; - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if(G.getPartitionIndex(target) == config.k) {//unassigned - queues[block].push(target); - } - } endfor - } - } - - unsigned no_unassigned = unassigned_vertices.size(); - while(no_unassigned > 0) { - PartitionID block = 0; - NodeWeight min_weight = G.number_of_nodes(); - for( unsigned cur_block = 0; cur_block < config.k; cur_block++) { - if( block_weights[cur_block] < min_weight) { - block = cur_block; - min_weight = block_weights[cur_block]; - } - } - - if( queues[block].size() != 0) { - NodeID front_node = queues[block].front(); - queues[block].pop(); - if(G.getPartitionIndex(front_node) == config.k) { //safe to assign - G.setPartitionIndex(front_node, block); - block_weights[block]++; - no_unassigned--; - forall_out_edges(G, e, front_node) { - NodeID target = G.getEdgeTarget(e); - if(G.getPartitionIndex(target) == config.k) { - queues[block].push(target); - } - } endfor - } - } else { - if( unassigned_vertices.size() > 0) { - NodeID node = unassigned_vertices[0]; - do { - unsigned idx = random_functions::nextInt(0, unassigned_vertices.size()-1); - node = unassigned_vertices[idx]; - if( G.getPartitionIndex(node) != config.k ) { - std::swap(unassigned_vertices[idx], - unassigned_vertices[unassigned_vertices.size() -1]); - - unassigned_vertices.pop_back(); - } else { - queues[block].push(node); - break; - } - } while( unassigned_vertices.size() != 0 ); - } - } - - - } + std::vector< std::queue< NodeID > > queues(config.k); + std::vector< NodeID > unassigned_vertices; + std::vector< std::vector< NodeID > > blocks(config.k); + + forall_nodes(G, node) { + if( G.getPartitionIndex(node) == config.k ) { + unassigned_vertices.push_back(node); + } else { + blocks[G.getPartitionIndex(node)].push_back(node); + } + } endfor + + + //shuffle the blocks + std::vector< NodeWeight > block_weights(config.k, 0); + for( unsigned block = 0; block < config.k; block++) { + random_functions::permutate_vector_good(blocks[block], false); + block_weights[block] = blocks[block].size(); + } + + for( unsigned block = 0; block < config.k; block++) { + for( unsigned j = 0; j < blocks[block].size(); j++) { + NodeID node = blocks[block][j]; + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + if(G.getPartitionIndex(target) == config.k) {//unassigned + queues[block].push(target); + } + } endfor + } + } + + unsigned no_unassigned = unassigned_vertices.size(); + while(no_unassigned > 0) { + PartitionID block = 0; + NodeWeight min_weight = G.number_of_nodes(); + for( unsigned cur_block = 0; cur_block < config.k; cur_block++) { + if( block_weights[cur_block] < min_weight) { + block = cur_block; + min_weight = block_weights[cur_block]; + } + } + + if( queues[block].size() != 0) { + NodeID front_node = queues[block].front(); + queues[block].pop(); + if(G.getPartitionIndex(front_node) == config.k) { //safe to assign + G.setPartitionIndex(front_node, block); + block_weights[block]++; + no_unassigned--; + forall_out_edges(G, e, front_node) { + NodeID target = G.getEdgeTarget(e); + if(G.getPartitionIndex(target) == config.k) { + queues[block].push(target); + } + } endfor + } + } else { + if( unassigned_vertices.size() > 0) { + NodeID node = unassigned_vertices[0]; + do { + unsigned idx = random_functions::nextInt(0, unassigned_vertices.size()-1); + node = unassigned_vertices[idx]; + if( G.getPartitionIndex(node) != config.k ) { + std::swap(unassigned_vertices[idx], + unassigned_vertices[unassigned_vertices.size() -1]); + + unassigned_vertices.pop_back(); + } else { + queues[block].push(node); + break; + } + } while( unassigned_vertices.size() != 0 ); + } + } + + + } } void construct_partition::createIndividuum( PartitionConfig & config, graph_access & G, Individuum & ind, bool output) { - std::cout << "creating individuum " << std::endl; - forall_nodes(G, node) { - G.setPartitionIndex(node, config.k); - } endfor - - for( unsigned block = 0; block < config.k; block++) { - NodeID node = 0; - do { - node = random_functions::nextInt(0, G.number_of_nodes() - 1); - } while( G.getPartitionIndex(node) != config.k ); - - G.setPartitionIndex(node, block); - } - - construct_starting_from_partition( config, G); - - complete_boundary boundary(&G); - boundary.build(); - - tabu_search ts; - PartitionConfig copy = config; - copy.maxIter = G.number_of_nodes(); - - ts.perform_refinement( copy, G, boundary); - - int* partition_map = new int[G.number_of_nodes()]; - forall_nodes(G, node) { - partition_map[node] = G.getPartitionIndex(node); - } endfor - - quality_metrics qm; - ind.objective = qm.objective(config, G, partition_map); - ind.partition_map = partition_map; - ind.cut_edges = new std::vector(); - - forall_nodes(G, node) { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if(partition_map[node] != partition_map[target]) { - ind.cut_edges->push_back(e); - } - } endfor - } endfor + std::cout << "creating individuum " << std::endl; + forall_nodes(G, node) { + G.setPartitionIndex(node, config.k); + } endfor + + for( unsigned block = 0; block < config.k; block++) { + NodeID node = 0; + do { + node = random_functions::nextInt(0, G.number_of_nodes() - 1); + } while( G.getPartitionIndex(node) != config.k ); + + G.setPartitionIndex(node, block); + } + + construct_starting_from_partition( config, G); + + complete_boundary boundary(&G); + boundary.build(); + + tabu_search ts; + PartitionConfig copy = config; + copy.maxIter = G.number_of_nodes(); + + ts.perform_refinement( copy, G, boundary); + + int* partition_map = new int[G.number_of_nodes()]; + forall_nodes(G, node) { + partition_map[node] = G.getPartitionIndex(node); + } endfor + + quality_metrics qm; + ind.objective = qm.objective(config, G, partition_map); + ind.partition_map = partition_map; + ind.cut_edges = new std::vector(); + + forall_nodes(G, node) { + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + if(partition_map[node] != partition_map[target]) { + ind.cut_edges->push_back(e); + } + } endfor +} endfor } +} \ No newline at end of file diff --git a/parallel/modified_kahip/lib/parallel_mh/galinier_combine/construct_partition.h b/parallel/modified_kahip/lib/parallel_mh/galinier_combine/construct_partition.h index 6a464d4a..fd4b0eed 100644 --- a/parallel/modified_kahip/lib/parallel_mh/galinier_combine/construct_partition.h +++ b/parallel/modified_kahip/lib/parallel_mh/galinier_combine/construct_partition.h @@ -11,16 +11,22 @@ #include "data_structure/graph_access.h" #include "parallel_mh/population.h" #include "partition_config.h" - +namespace kahip::modified { class construct_partition { public: - construct_partition(); - virtual ~construct_partition(); + construct_partition() = default; // Default constructor + virtual ~construct_partition() = default; // Default destructor - void construct_starting_from_partition( PartitionConfig & config, graph_access & G); - void createIndividuum( PartitionConfig & config, graph_access & G, - Individuum & ind, bool output); -}; + // Explicitly default the other special member functions + construct_partition(const construct_partition&) = default; // Copy constructor + construct_partition& operator=(const construct_partition&) = default; // Copy assignment operator + construct_partition(construct_partition&&) = default; // Move constructor + construct_partition& operator=(construct_partition&&) = default; // Move assignment operator + void construct_starting_from_partition( PartitionConfig & config, graph_access & G); + void createIndividuum( PartitionConfig & config, graph_access & G, + Individuum & ind, bool output); +}; +} #endif /* end of include guard: CONSTRUCT_PARTITION_E86DQF5S */ diff --git a/parallel/modified_kahip/lib/parallel_mh/galinier_combine/gal_combine.cpp b/parallel/modified_kahip/lib/parallel_mh/galinier_combine/gal_combine.cpp index 5fb015be..045f4f66 100644 --- a/parallel/modified_kahip/lib/parallel_mh/galinier_combine/gal_combine.cpp +++ b/parallel/modified_kahip/lib/parallel_mh/galinier_combine/gal_combine.cpp @@ -17,98 +17,91 @@ #include "uncoarsening/refinement/mixed_refinement.h" #include "uncoarsening/refinement/refinement.h" #include "uncoarsening/refinement/tabu_search/tabu_search.h" - -gal_combine::gal_combine() { - -} - -gal_combine::~gal_combine() { - -} - +namespace kahip::modified { // implements our version of gal combine // compute a matching between blocks (greedily) // extend to partition // apply our refinements and tabu search void gal_combine::perform_gal_combine( PartitionConfig & config, graph_access & G) { - //first greedily compute a matching of the partitions - std::vector< std::unordered_map > counters(config.k); - forall_nodes(G, node) { - //boundary_pair bp; - if(counters[G.getPartitionIndex(node)].find(G.getSecondPartitionIndex(node)) != counters[G.getPartitionIndex(node)].end()) { - counters[G.getPartitionIndex(node)][G.getSecondPartitionIndex(node)] += 1; - } else { - counters[G.getPartitionIndex(node)][G.getSecondPartitionIndex(node)] = 1; - } - } endfor - - std::vector< PartitionID > permutation(config.k); - for( unsigned i = 0; i < permutation.size(); i++) { - permutation[i] = i; - } - - random_functions::permutate_vector_good_small(permutation); - std::vector rhs_matched(config.k, false); - std::vector bipartite_matching(config.k); - for( unsigned i = 0; i < permutation.size(); i++) { - PartitionID cur_partition = permutation[i]; - PartitionID best_unassigned = config.k; - NodeWeight best_value = 0; - - for( std::unordered_map::iterator it = counters[cur_partition].begin(); - it != counters[cur_partition].end(); ++it) { - if( rhs_matched[it->first] == false && it->second > best_value ) { - best_unassigned = it->first; - best_value = it->second; - } - } - - bipartite_matching[cur_partition] = best_unassigned; - if( best_unassigned != config.k ) { - rhs_matched[best_unassigned] = true; - } - } - - std::vector blocked_vertices(G.number_of_nodes(), false); - forall_nodes(G, node) { - if( bipartite_matching[G.getPartitionIndex(node)] == G.getSecondPartitionIndex(node) ){ - blocked_vertices[node] = true; - } else { - // we will reassign this vertex since the partitions do not agree on it - G.setPartitionIndex(node, config.k); - } - } endfor - - construct_partition cp; - cp.construct_starting_from_partition( config, G ); - - refinement* refine = new mixed_refinement(); - - double real_epsilon = config.imbalance/100.0; - double epsilon = random_functions::nextDouble(real_epsilon+0.005,real_epsilon+config.kabaE_internal_bal); - PartitionConfig copy = config; - copy.upper_bound_partition = (1+epsilon)*ceil(config.largest_graph_weight/(double)config.k); - - complete_boundary boundary(&G); - boundary.build(); - - tabu_search ts; - ts.perform_refinement( copy, G, boundary); - - //now obtain the quotient graph - complete_boundary boundary2(&G); - boundary2.build(); - - copy = config; - copy.upper_bound_partition = (1+epsilon)*ceil(config.largest_graph_weight/(double)config.k); - - refine->perform_refinement( copy, G, boundary2); - - copy = config; - cycle_refinement cr; - cr.perform_refinement(config, G, boundary2); - delete refine; + //first greedily compute a matching of the partitions + std::vector< std::unordered_map > counters(config.k); + forall_nodes(G, node) { + //boundary_pair bp; + if(counters[G.getPartitionIndex(node)].find(G.getSecondPartitionIndex(node)) != counters[G.getPartitionIndex(node)].end()) { + counters[G.getPartitionIndex(node)][G.getSecondPartitionIndex(node)] += 1; + } else { + counters[G.getPartitionIndex(node)][G.getSecondPartitionIndex(node)] = 1; + } + } endfor + + std::vector< PartitionID > permutation(config.k); + for( unsigned i = 0; i < permutation.size(); i++) { + permutation[i] = i; + } + + random_functions::permutate_vector_good_small(permutation); + std::vector rhs_matched(config.k, false); + std::vector bipartite_matching(config.k); + for( unsigned i = 0; i < permutation.size(); i++) { + PartitionID cur_partition = permutation[i]; + PartitionID best_unassigned = config.k; + NodeWeight best_value = 0; + + for( std::unordered_map::iterator it = counters[cur_partition].begin(); + it != counters[cur_partition].end(); ++it) { + if( rhs_matched[it->first] == false && it->second > best_value ) { + best_unassigned = it->first; + best_value = it->second; + } + } + + bipartite_matching[cur_partition] = best_unassigned; + if( best_unassigned != config.k ) { + rhs_matched[best_unassigned] = true; + } + } + + std::vector blocked_vertices(G.number_of_nodes(), false); + forall_nodes(G, node) { + if( bipartite_matching[G.getPartitionIndex(node)] == G.getSecondPartitionIndex(node) ){ + blocked_vertices[node] = true; + } else { + // we will reassign this vertex since the partitions do not agree on it + G.setPartitionIndex(node, config.k); + } + } endfor + + construct_partition cp; + cp.construct_starting_from_partition( config, G ); + + refinement* refine = new mixed_refinement(); + + double real_epsilon = config.imbalance/100.0; + double epsilon = random_functions::nextDouble(real_epsilon+0.005,real_epsilon+config.kabaE_internal_bal); + PartitionConfig copy = config; + copy.upper_bound_partition = (1+epsilon)*ceil(config.largest_graph_weight/(double)config.k); + + complete_boundary boundary(&G); + boundary.build(); + + tabu_search ts; + ts.perform_refinement( copy, G, boundary); + + //now obtain the quotient graph + complete_boundary boundary2(&G); + boundary2.build(); + + copy = config; + copy.upper_bound_partition = (1+epsilon)*ceil(config.largest_graph_weight/(double)config.k); + + refine->perform_refinement( copy, G, boundary2); + + copy = config; + cycle_refinement cr; + cr.perform_refinement(config, G, boundary2); + delete refine; } +} \ No newline at end of file diff --git a/parallel/modified_kahip/lib/parallel_mh/galinier_combine/gal_combine.h b/parallel/modified_kahip/lib/parallel_mh/galinier_combine/gal_combine.h index 58d0005f..0c823baf 100644 --- a/parallel/modified_kahip/lib/parallel_mh/galinier_combine/gal_combine.h +++ b/parallel/modified_kahip/lib/parallel_mh/galinier_combine/gal_combine.h @@ -10,14 +10,20 @@ #include "partition_config.h" #include "data_structure/graph_access.h" - +namespace kahip::modified { class gal_combine { public: - gal_combine(); - virtual ~gal_combine(); + gal_combine() = default; // Default constructor + virtual ~gal_combine() = default; // Default destructor - void perform_gal_combine( PartitionConfig & config, graph_access & G); -}; + // Explicitly default the other special member functions + gal_combine(const gal_combine&) = default; // Copy constructor + gal_combine& operator=(const gal_combine&) = default; // Copy assignment operator + gal_combine(gal_combine&&) = default; // Move constructor + gal_combine& operator=(gal_combine&&) = default; // Move assignment operator + void perform_gal_combine( PartitionConfig & config, graph_access & G); +}; +} #endif /* end of include guard: GAL_COMBINE_XDMU5YB7 */ diff --git a/parallel/modified_kahip/lib/parallel_mh/parallel_mh_async.cpp b/parallel/modified_kahip/lib/parallel_mh/parallel_mh_async.cpp index 9b34817b..543630f4 100644 --- a/parallel/modified_kahip/lib/parallel_mh/parallel_mh_async.cpp +++ b/parallel/modified_kahip/lib/parallel_mh/parallel_mh_async.cpp @@ -6,11 +6,14 @@ *****************************************************************************/ #include -#include +#include +#include +#include #include +#include #include #include -#include +#include #include "diversifyer.h" #include "exchange/exchanger.h" @@ -18,312 +21,287 @@ #include "graph_io.h" #include "graph_partitioner.h" #include "parallel_mh_async.h" +#include "parallel_mh/evolutionary_collectives.h" +#include "parallel_mh/evolutionary_feasibility.h" +#include "parallel_mh/population_size_broadcast.h" +#include "../../../shared/random_state.h" #include "quality_metrics.h" #include "random_functions.h" +namespace kahip::modified { +parallel_mh_async::parallel_mh_async() + : parallel_mh_async(MPI_COMM_WORLD) {} -parallel_mh_async::parallel_mh_async(): MASTER(0), m_time_limit(0) { - m_communicator = MPI_COMM_WORLD; - m_best_global_objective = std::numeric_limits::max(); - m_best_cycle_objective = std::numeric_limits::max(); - m_rounds = 0; - m_termination = false; - MPI_Comm_rank( m_communicator, &m_rank); - MPI_Comm_size( m_communicator, &m_size); -} - -parallel_mh_async::parallel_mh_async(MPI_Comm communicator) : MASTER(0), m_time_limit(0) { - m_best_global_objective = std::numeric_limits::max(); - m_best_cycle_objective = std::numeric_limits::max(); - m_rounds = 0; - m_termination = false; - m_communicator = communicator; - MPI_Comm_rank( m_communicator, &m_rank); - MPI_Comm_size( m_communicator, &m_size); - -} +parallel_mh_async::parallel_mh_async(MPI_Comm communicator) + : m_communicator( + std::make_unique< + ::kahip::parallel_mh::owned_evolutionary_communicator>( + communicator)), + m_rank(m_communicator->rank()), + m_size(m_communicator->size()) {} -parallel_mh_async::~parallel_mh_async() { - delete[] m_best_global_map; -} +parallel_mh_async::~parallel_mh_async() = default; void parallel_mh_async::perform_partitioning(const PartitionConfig & partition_config, graph_access & G) { - m_time_limit = partition_config.time_limit; - m_island = new population(m_communicator, partition_config); - m_best_global_map = new PartitionID[G.number_of_nodes()]; - - srand(partition_config.seed*m_size+m_rank); - random_functions::setSeed(partition_config.seed*m_size+m_rank); - - PartitionConfig ini_working_config = partition_config; - initialize( ini_working_config, G); - - m_t.restart(); - if( !partition_config.ultra_fast_kaffpaE_interfacecall ) { - exchanger ex(m_communicator); - do { - PartitionConfig working_config = partition_config; - - working_config.graph_allready_partitioned = false; - if(!partition_config.strong) - working_config.no_new_initial_partitioning = false; - - working_config.mh_pool_size = ini_working_config.mh_pool_size; - if(m_rounds == 0 && working_config.mh_enable_quickstart) { - ex.quick_start( working_config, G, *m_island ); - } - - perform_local_partitioning( working_config, G ); - if(m_rank == ROOT) { - std::cout << "t left " << (m_time_limit - m_t.elapsed()) << std::endl; - } - - //push and recv - if( m_t.elapsed() <= m_time_limit && m_size > 1) { - unsigned messages = ceil(log(m_size)); - for( unsigned i = 0; i < messages; i++) { - ex.push_best( working_config, G, *m_island ); - ex.recv_incoming( working_config, G, *m_island ); - } - } - - m_rounds++; - } while( m_t.elapsed() <= m_time_limit ); + m_time_limit = partition_config.time_limit; + m_rounds = 0; + m_island = std::make_unique(m_communicator->native_handle(), + partition_config); + + auto const local_seed = ::kahip::random_compat::mixed_rank_seed( + partition_config.seed, m_size, m_rank); + if (!local_seed.has_value()) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator->native_handle(), "evolutionary random seed derivation", + "process count and rank must identify a valid communicator member"); + } + std::srand(*local_seed); + random_functions::setSeed(*local_seed); + + PartitionConfig ini_working_config = partition_config; + initialize( ini_working_config, G); + + m_t.restart(); + if( !partition_config.ultra_fast_kaffpaE_interfacecall ) { + // Isend/Iprobe intentionally implement the paper's asynchronous + // evolutionary rumor spreading, not a collective-shaped redistribution. + // exchanger::finish still gives every request and payload an exact, + // collective teardown lifetime before this scope ends. + exchanger ex(m_communicator->native_handle()); + do { + PartitionConfig working_config = partition_config; + + working_config.graph_allready_partitioned = false; + if(!partition_config.strong) + working_config.no_new_initial_partitioning = false; + + working_config.mh_pool_size = ini_working_config.mh_pool_size; + if(m_rounds == 0 && working_config.mh_enable_quickstart) { + ex.quick_start( working_config, G, *m_island ); + } + + perform_local_partitioning( working_config, G ); + if(m_rank == ROOT) { + std::cout << "t left " << (m_time_limit - m_t.elapsed()) << std::endl; + } + + //push and recv + if( m_t.elapsed() <= m_time_limit && m_size > 1) { + auto const messages = + static_cast(std::ceil(std::log(m_size))); + for( unsigned i = 0; i < messages; i++) { + ex.push_best( working_config, G, *m_island ); + ex.recv_incoming( working_config, G, *m_island ); } - - collect_best_partitioning(G, partition_config); - m_island->print(); - - //print logfile (for convergence plots) - if( partition_config.mh_print_log ) { - std::stringstream filename_stream; - filename_stream << "log_"<< partition_config.graph_filename << - "_m_rank_" << m_rank << - "_file_" << - "_seed_" << partition_config.seed << - "_k_" << partition_config.k; - - std::string filename(filename_stream.str()); - m_island->write_log(filename); - } - - delete m_island; + } + + m_rounds++; + } while( m_t.elapsed() <= m_time_limit ); + ex.finish(static_cast(G.number_of_nodes())); + } + + EdgeWeight min_objective = 0; + m_island->apply_fittest(G, min_objective); + collect_best_partitioning(G, partition_config, min_objective); + m_island->print(); + + //print logfile (for convergence plots) + if( partition_config.mh_print_log ) { + std::stringstream filename_stream; + filename_stream << "log_"<< partition_config.graph_filename << + "_m_rank_" << m_rank << + "_file_" << + "_seed_" << partition_config.seed << + "_k_" << partition_config.k; + + std::string filename(filename_stream.str()); + m_island->write_log(filename); + } + + m_island.reset(); } void parallel_mh_async::initialize(PartitionConfig & working_config, graph_access & G) { - // each PE performs a partitioning - // estimate the runtime of a partitioner call - // calculate the poolsize and async Bcast the poolsize. - // recv. has to be sync - Individuum first_one; - m_t.restart(); - if( !working_config.mh_easy_construction) { - m_island->createIndividuum( working_config, G, first_one, true); - } else { - construct_partition cp; - cp.createIndividuum( working_config, G, first_one, true); - std::cout << "created with objective " << first_one.objective << std::endl; - } - - double time_spend = m_t.elapsed(); - m_island->insert(G, first_one); - - if( working_config.ultra_fast_kaffpaE_interfacecall ) { - working_config.mh_pool_size = 1; - m_island->set_pool_size(1); - return; - } - - - //compute S and Bcast - int population_size = 1; - double fraction = working_config.mh_initial_population_fraction; - int POPSIZE_TAG = 10; - - if( m_rank == ROOT ) { - double fraction_to_spend_for_IP = (double)m_time_limit / fraction; - population_size = ceil(fraction_to_spend_for_IP / time_spend); - - for( int target = 1; target < m_size; target++) { - MPI_Request rq; - MPI_Isend(&population_size, 1, MPI_INT, target, POPSIZE_TAG, m_communicator, &rq); - } - } else { - MPI_Status rst; - MPI_Recv(&population_size, 1, MPI_INT, ROOT, POPSIZE_TAG, m_communicator, &rst); - } - - population_size = std::max(3, population_size); - if(working_config.mh_easy_construction) { - population_size = std::min(50, population_size); - } else { - population_size = std::min(100, population_size); - } - std::cout << "poolsize = " << population_size << std::endl; - - //set S - m_island->set_pool_size(population_size); - working_config.mh_pool_size = population_size; + // each PE performs a partitioning + // estimate the runtime of a partitioner call + // calculate the poolsize and broadcast it to the communicator. + Individuum first_one; + m_t.restart(); + if( !working_config.mh_easy_construction) { + m_island->createIndividuum( working_config, G, first_one, true); + } else { + construct_partition cp; + cp.createIndividuum( working_config, G, first_one, true); + std::cout << "created with objective " << first_one.objective << std::endl; + } + + double time_spend = m_t.elapsed(); + m_island->insert(G, first_one); + + if( working_config.ultra_fast_kaffpaE_interfacecall ) { + working_config.mh_pool_size = 1; + m_island->set_pool_size(1); + return; + } + + + //compute S and Bcast + int population_size = 1; + double fraction = working_config.mh_initial_population_fraction; + + if( m_rank == ROOT ) { + auto const estimate = ::kahip::parallel_mh::estimate_population_size( + m_time_limit, fraction, time_spend, + working_config.mh_easy_construction); + if (!estimate.has_value()) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator->native_handle(), + "evolutionary population-size estimation", + "time limit, initial fraction, and elapsed time must be finite and " + "within their valid domains"); + } + population_size = *estimate; + } + + population_size = ::kahip::parallel_mh::broadcast_population_size( + m_communicator->native_handle(), population_size, + working_config.mh_easy_construction); + std::cout << "poolsize = " << population_size << std::endl; + + //set S + m_island->set_pool_size(population_size); + working_config.mh_pool_size = population_size; } -EdgeWeight parallel_mh_async::collect_best_partitioning(graph_access & G, const PartitionConfig & config) { - //perform partitioning locally - EdgeWeight min_objective = 0; - m_island->apply_fittest(G, min_objective); - - int best_local_objective = min_objective; - int best_local_objective_m = min_objective; - int best_global_objective = 0; - - PartitionID* best_local_map = new PartitionID[G.number_of_nodes()]; - std::vector< NodeWeight > block_sizes(G.get_partition_count(),0); - - forall_nodes(G, node) { - best_local_map[node] = G.getPartitionIndex(node); - block_sizes[G.getPartitionIndex(node)]++; - } endfor - - NodeWeight max_domain_weight = 0; - for( unsigned i = 0; i < G.get_partition_count(); i++) { - if( block_sizes[i] > max_domain_weight ) { - max_domain_weight = block_sizes[i]; - } - } - - if( max_domain_weight > config.upper_bound_partition ) { - best_local_objective_m = std::numeric_limits< int >::max(); - } - - MPI_Allreduce(&best_local_objective_m, &best_global_objective, 1, MPI_INT, MPI_MIN, m_communicator); - - if( best_global_objective == std::numeric_limits< int >::max()) { - //no partition is feasible - MPI_Allreduce(&best_local_objective, &best_global_objective, 1, MPI_INT, MPI_MIN, m_communicator); - } - - int my_domain_weight = best_local_objective == best_global_objective ? - max_domain_weight : std::numeric_limits::max(); - int best_domain_weight = max_domain_weight; - - MPI_Allreduce(&my_domain_weight, &best_domain_weight, 1, MPI_INT, MPI_MIN, m_communicator); - - // now we know what the best objective is ... find the best balance - int bcaster = best_local_objective == best_global_objective - && my_domain_weight == best_domain_weight ? m_rank : std::numeric_limits::max(); - int g_bcaster = 0; - - MPI_Allreduce(&bcaster, &g_bcaster, 1, MPI_INT, MPI_MIN, m_communicator); - MPI_Bcast(best_local_map, G.number_of_nodes(), MPI_INT, g_bcaster, m_communicator); - - forall_nodes(G, node) { - G.setPartitionIndex(node, best_local_map[node]); - } endfor - - delete[] best_local_map; - - return best_global_objective; +EdgeWeight parallel_mh_async::collect_best_partitioning( + graph_access& G, + PartitionConfig const& config, + EdgeWeight min_objective) { + std::vector best_local_map(G.number_of_nodes()); + forall_nodes(G, node) { + best_local_map[node] = G.getPartitionIndex(node); + } endfor + + auto const max_domain_weight = + ::kahip::parallel_mh::maximum_block_weight(G); + if (!max_domain_weight.has_value()) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator->native_handle(), "evolutionary feasibility accounting", + "partition labels or block-weight sums exceed their valid domains"); + } + + auto const best_global_objective = + ::kahip::parallel_mh::select_and_broadcast_best_partition( + m_communicator->native_handle(), min_objective, *max_domain_weight, + config.upper_bound_partition, best_local_map.data(), + best_local_map.size()); + + forall_nodes(G, node) { + G.setPartitionIndex(node, best_local_map[node]); + } endfor + + return best_global_objective; } EdgeWeight parallel_mh_async::perform_local_partitioning(PartitionConfig & working_config, graph_access & G) { - quality_metrics qm; - unsigned local_repetitions = working_config.local_partitioning_repetitions; + quality_metrics qm; + unsigned local_repetitions = working_config.local_partitioning_repetitions; + + if( working_config.mh_diversify ) { + diversifyer div; + div.diversify(working_config); + } + + //start a new round + for( unsigned i = 0; i < local_repetitions; i++) { + if( working_config.mh_no_mh ) { + Individuum first_ind; + + if( !working_config.mh_easy_construction) { + m_island->createIndividuum(working_config, G, first_ind, true); + m_island->insert(G, first_ind); + } else { + construct_partition cp; + cp.createIndividuum( working_config, G, first_ind, true); + + m_island->insert(G, first_ind); + std::cout << "created with objective " << first_ind.objective << std::endl; + } + } else { + if( m_island->is_full() && !working_config.mh_disable_combine) { + + int decision = random_functions::nextInt(0,9); + Individuum output; + + if(decision < working_config.mh_flip_coin) { + m_island->mutate_random(working_config, G, output); + m_island->insert(G, output); + } else { - if( working_config.mh_diversify ) { - diversifyer div; - div.diversify(working_config); + int combine_decision = random_functions::nextInt(0,5); + if(combine_decision <= 4) { + Individuum first_rnd; + Individuum second_rnd; + if(working_config.mh_enable_tournament_selection) { + m_island->get_two_individuals_tournament(first_rnd, second_rnd); + } else { + m_island->get_two_random_individuals(first_rnd, second_rnd); + } + + m_island->combine(working_config, G, first_rnd, second_rnd, output); + + int coin = 0; + + if( working_config.mh_enable_gal_combine ) { + coin = random_functions::nextInt(0,100); + } + if( coin == 23 ) { + if( first_rnd.objective > second_rnd.objective) { + m_island->replace(first_rnd, output); + } else { + m_island->replace(second_rnd, output); + } + } else { + m_island->insert(G, output); + } + } else if( combine_decision == 5 ) { + if(!working_config.mh_disable_cross_combine) { + Individuum selected; + m_island->get_one_individual_tournament(selected); + m_island->combine_cross(working_config, G, selected, output); + m_island->insert(G, output); + } + } } - //start a new round - for( unsigned i = 0; i < local_repetitions; i++) { - if( working_config.mh_no_mh ) { - Individuum first_ind; - - if( !working_config.mh_easy_construction) { - m_island->createIndividuum(working_config, G, first_ind, true); - m_island->insert(G, first_ind); - } else { - construct_partition cp; - cp.createIndividuum( working_config, G, first_ind, true); - - m_island->insert(G, first_ind); - std::cout << "created with objective " << first_ind.objective << std::endl; - } - } else { - if( m_island->is_full() && !working_config.mh_disable_combine) { - - int decision = random_functions::nextInt(0,9); - Individuum output; - - if(decision < working_config.mh_flip_coin) { - m_island->mutate_random(working_config, G, output); - m_island->insert(G, output); - } else { - - int combine_decision = random_functions::nextInt(0,5); - if(combine_decision <= 4) { - Individuum first_rnd; - Individuum second_rnd; - if(working_config.mh_enable_tournament_selection) { - m_island->get_two_individuals_tournament(first_rnd, second_rnd); - } else { - m_island->get_two_random_individuals(first_rnd, second_rnd); - } - - m_island->combine(working_config, G, first_rnd, second_rnd, output); - - int coin = 0; - - if( working_config.mh_enable_gal_combine ) { - coin = random_functions::nextInt(0,100); - } - if( coin == 23 ) { - if( first_rnd.objective > second_rnd.objective) { - m_island->replace(first_rnd, output); - } else { - m_island->replace(second_rnd, output); - } - } else { - m_island->insert(G, output); - } - } else if( combine_decision == 5 ) { - if(!working_config.mh_disable_cross_combine) { - Individuum selected; - m_island->get_one_individual_tournament(selected); - m_island->combine_cross(working_config, G, selected, output); - m_island->insert(G, output); - } - } - } - - } else { - Individuum first_ind; - if(m_island->is_full()) { - m_island->mutate_random(working_config, G, first_ind); - } else { - if( !working_config.mh_easy_construction) { - m_island->createIndividuum(working_config, G, first_ind, true); - } else { - construct_partition cp; - cp.createIndividuum( working_config, G, first_ind, true); - std::cout << "created with objective " << first_ind.objective << std::endl; - } - } - m_island->insert(G, first_ind); - } - } - - //try to combine to random inidividuals from pool - if( m_t.elapsed() > m_time_limit ) { - break; - } - + } else { + Individuum first_ind; + if(m_island->is_full()) { + m_island->mutate_random(working_config, G, first_ind); + } else { + if( !working_config.mh_easy_construction) { + m_island->createIndividuum(working_config, G, first_ind, true); + } else { + construct_partition cp; + cp.createIndividuum( working_config, G, first_ind, true); + std::cout << "created with objective " << first_ind.objective << std::endl; + } } + m_island->insert(G, first_ind); + } + } - EdgeWeight min_objective = 0; - m_island->apply_fittest(G, min_objective); + //try to combine to random inidividuals from pool + if( m_t.elapsed() > m_time_limit ) { + break; + } - return min_objective; -} + } + EdgeWeight min_objective = 0; + m_island->apply_fittest(G, min_objective); + return min_objective; +} +} diff --git a/parallel/modified_kahip/lib/parallel_mh/parallel_mh_async.h b/parallel/modified_kahip/lib/parallel_mh/parallel_mh_async.h index e54c97f2..26def3cb 100644 --- a/parallel/modified_kahip/lib/parallel_mh/parallel_mh_async.h +++ b/parallel/modified_kahip/lib/parallel_mh/parallel_mh_async.h @@ -9,42 +9,50 @@ #define PARALLEL_MH_ASYNC_HF106Y0G #include + +#include + #include "data_structure/graph_access.h" #include "partition_config.h" #include "population.h" #include "timer.h" - -class parallel_mh_async { -public: - parallel_mh_async(); - parallel_mh_async(MPI_Comm communicator); - virtual ~parallel_mh_async(); - - void perform_partitioning(const PartitionConfig & graph_partitioner_config, graph_access & G); - void initialize(PartitionConfig & graph_partitioner_config, graph_access & G); - EdgeWeight perform_local_partitioning(PartitionConfig & graph_partitioner_config, graph_access & G); - EdgeWeight collect_best_partitioning(graph_access & G, const PartitionConfig & config); - void perform_cycle_partitioning(PartitionConfig & graph_partitioner_config, graph_access & G); - -private: - //misc - const unsigned MASTER; - timer m_t; - int m_rank; - int m_size; - double m_time_limit; - bool m_termination; - unsigned m_rounds; - - //the best cut found so far - PartitionID* m_best_global_map; - int m_best_global_objective; - int m_best_cycle_objective; - - //island - population* m_island; - MPI_Comm m_communicator; +namespace kahip::parallel_mh { +class owned_evolutionary_communicator; +} +namespace kahip::modified { +class parallel_mh_async final { + public: + parallel_mh_async(); + explicit parallel_mh_async(MPI_Comm communicator); + ~parallel_mh_async(); + + parallel_mh_async(parallel_mh_async const&) = delete; + auto operator=(parallel_mh_async const&) -> parallel_mh_async& = delete; + parallel_mh_async(parallel_mh_async&&) = delete; + auto operator=(parallel_mh_async&&) -> parallel_mh_async& = delete; + + void perform_partitioning(PartitionConfig const& graph_partitioner_config, + graph_access& G); + void initialize(PartitionConfig& graph_partitioner_config, graph_access& G); + EdgeWeight perform_local_partitioning( + PartitionConfig& graph_partitioner_config, + graph_access& G); + EdgeWeight collect_best_partitioning(graph_access& G, + PartitionConfig const& config, + EdgeWeight min_objective); + void perform_cycle_partitioning(PartitionConfig& graph_partitioner_config, + graph_access& G); + + private: + std::unique_ptr<::kahip::parallel_mh::owned_evolutionary_communicator> + m_communicator; + timer m_t; + int m_rank; + int m_size; + double m_time_limit = 0.0; + unsigned m_rounds = 0; + std::unique_ptr m_island; }; - +} // namespace kahip::modified #endif /* end of include guard: PARALLEL_MH_ASYNC_HF106Y0G */ diff --git a/parallel/modified_kahip/lib/parallel_mh/population.cpp b/parallel/modified_kahip/lib/parallel_mh/population.cpp index 00ac9be0..f1cde4db 100644 --- a/parallel/modified_kahip/lib/parallel_mh/population.cpp +++ b/parallel/modified_kahip/lib/parallel_mh/population.cpp @@ -11,428 +11,456 @@ #include #include #include +#include #include "diversifyer.h" #include "galinier_combine/gal_combine.h" #include "graph_partitioner.h" +#include "parallel_mh/evolutionary_collectives.h" #include "population.h" #include "quality_metrics.h" #include "random_functions.h" #include "timer.h" #include "uncoarsening/refinement/cycle_improvements/cycle_refinement.h" - -population::population( MPI_Comm communicator, const PartitionConfig & partition_config ) { - m_population_size = partition_config.mh_pool_size; - m_no_partition_calls = 0; - m_num_NCs = partition_config.mh_num_ncs_to_compute; - m_num_NCs_computed = 0; - m_num_ENCs = 0; - m_time_stamp = 0; - m_communicator = communicator; - m_global_timer.restart(); +namespace kahip::modified { +namespace { +class null_streambuf final : public std::streambuf { + protected: + auto overflow(traits_type::int_type character) + -> traits_type::int_type override { + return traits_type::not_eof(character); + } +}; + +class scoped_output_suppression final { + public: + scoped_output_suppression() : previous_(std::cout.rdbuf(&sink_)) {} + ~scoped_output_suppression() { std::cout.rdbuf(previous_); } + + scoped_output_suppression(scoped_output_suppression const&) = delete; + auto operator=(scoped_output_suppression const&) + -> scoped_output_suppression& = delete; + + private: + null_streambuf sink_; + std::streambuf* previous_; +}; +} // namespace + +population::population(MPI_Comm communicator, + PartitionConfig const& partition_config) + : m_population_size(partition_config.mh_pool_size), + m_num_NCs(partition_config.mh_num_ncs_to_compute), + m_communicator(communicator) { + m_global_timer.restart(); } population::~population() { - for( unsigned i = 0; i < m_internal_population.size(); i++) { - delete[] (m_internal_population[i].partition_map); - delete m_internal_population[i].cut_edges; - } + for( unsigned i = 0; i < m_internal_population.size(); i++) { + delete[] (m_internal_population[i].partition_map); + delete m_internal_population[i].cut_edges; + } } void population::set_pool_size(int size) { - m_population_size = size; + m_population_size = size; } -void population::createIndividuum(const PartitionConfig & config, - graph_access & G, - Individuum & ind, bool output) { - - PartitionConfig copy = config; - graph_partitioner partitioner; - quality_metrics qm; - - std::ofstream ofs; - std::streambuf* backup = std::cout.rdbuf(); - ofs.open("/dev/null"); - std::cout.rdbuf(ofs.rdbuf()); - - timer t; t.restart(); - - if(config.buffoon) { // graph is weighted -> no negative cycle detection yet - partitioner.perform_partitioning(copy, G); - ofs.close(); - std::cout.rdbuf(backup); - } else { - if(config.kabapE) { - double real_epsilon = config.imbalance/100.0; - double lb = real_epsilon+0.005; - double ub = real_epsilon+config.kabaE_internal_bal; - double epsilon = random_functions::nextDouble(lb,ub); - copy.upper_bound_partition = (1+epsilon)*ceil(config.largest_graph_weight/(double)config.k); - - partitioner.perform_partitioning(copy, G); - - ofs.close(); - std::cout.rdbuf(backup); - - complete_boundary boundary(&G); - boundary.build(); - - copy = config; - - diversifyer df; - df.diversify_kaba(copy); - - cycle_refinement cr; - cr.perform_refinement(copy, G, boundary); - } else { - partitioner.perform_partitioning(copy, G); - ofs.close(); - std::cout.rdbuf(backup); - } - } - - int* partition_map = new int[G.number_of_nodes()]; - - forall_nodes(G, node) { - partition_map[node] = G.getPartitionIndex(node); - } endfor +void population::createIndividuum(const PartitionConfig & config, + graph_access & G, + Individuum & ind, bool output) { + + PartitionConfig copy = config; + graph_partitioner partitioner; + quality_metrics qm; + + if(config.buffoon) { // graph is weighted -> no negative cycle detection yet + auto suppression = scoped_output_suppression{}; + partitioner.perform_partitioning(copy, G); + } else { + if(config.kabapE) { + double real_epsilon = config.imbalance/100.0; + double lb = real_epsilon+0.005; + double ub = real_epsilon+config.kabaE_internal_bal; + double epsilon = random_functions::nextDouble(lb,ub); + copy.upper_bound_partition = (1+epsilon)*ceil(config.largest_graph_weight/(double)config.k); + + { + auto suppression = scoped_output_suppression{}; + partitioner.perform_partitioning(copy, G); + } + + complete_boundary boundary(&G); + boundary.build(); + + copy = config; + + diversifyer df; + df.diversify_kaba(copy); + + cycle_refinement cr; + cr.perform_refinement(copy, G, boundary); + } else { + auto suppression = scoped_output_suppression{}; + partitioner.perform_partitioning(copy, G); + } + } + + int* partition_map = new int[G.number_of_nodes()]; + + forall_nodes(G, node) { + partition_map[node] = G.getPartitionIndex(node); + } endfor + + ind.objective = qm.objective(config, G, partition_map); + ind.partition_map = partition_map; + ind.cut_edges = new std::vector(); + + forall_nodes(G, node) { + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + if(partition_map[node] != partition_map[target]) { + ind.cut_edges->push_back(e); + } + } endfor +} endfor + +if(output) { + m_filebuffer_string << m_global_timer.elapsed() << " " << ind.cut_edges->size()/2 << std::endl; + m_time_stamp++; +} +} - ind.objective = qm.objective(config, G, partition_map); - ind.partition_map = partition_map; - ind.cut_edges = new std::vector(); +void population::insert(graph_access & G, Individuum & ind) { - forall_nodes(G, node) { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if(partition_map[node] != partition_map[target]) { - ind.cut_edges->push_back(e); - } - } endfor - } endfor + m_no_partition_calls++; + if(m_internal_population.size() < m_population_size) { + m_internal_population.push_back(ind); + } else { + EdgeWeight worst_objective = 0; + for( unsigned i = 0; i < m_internal_population.size(); i++) { + if(m_internal_population[i].objective > worst_objective) { + worst_objective = m_internal_population[i].objective; + } + } + if(ind.objective > worst_objective ) { + delete[] (ind.partition_map); + delete ind.cut_edges; + return; // do nothing + } + //else measure similarity + unsigned max_similarity = std::numeric_limits::max(); + unsigned max_similarity_idx = 0; + for( unsigned i = 0; i < m_internal_population.size(); i++) { + if(m_internal_population[i].objective >= ind.objective) { + //now measure + int diff_size = m_internal_population[i].cut_edges->size() + ind.cut_edges->size(); + std::vector output_diff(diff_size,std::numeric_limits::max()); + + set_symmetric_difference(m_internal_population[i].cut_edges->begin(), + m_internal_population[i].cut_edges->end(), + ind.cut_edges->begin(), + ind.cut_edges->end(), + output_diff.begin()); + + unsigned similarity = 0; + for( unsigned j = 0; j < output_diff.size(); j++) { + if(output_diff[j] < std::numeric_limits::max()) { + similarity++; + } else { + break; + } + } - if(output) { - m_filebuffer_string << m_global_timer.elapsed() << " " << ind.cut_edges->size()/2 << std::endl; - m_time_stamp++; + if( similarity < max_similarity) { + max_similarity = similarity; + max_similarity_idx = i; } -} + } + } -void population::insert(graph_access & G, Individuum & ind) { + delete[] (m_internal_population[max_similarity_idx].partition_map); + delete m_internal_population[max_similarity_idx].cut_edges; - m_no_partition_calls++; - if(m_internal_population.size() < m_population_size) { - m_internal_population.push_back(ind); - } else { - EdgeWeight worst_objective = 0; - for( unsigned i = 0; i < m_internal_population.size(); i++) { - if(m_internal_population[i].objective > worst_objective) { - worst_objective = m_internal_population[i].objective; - } - } - if(ind.objective > worst_objective ) { - delete[] (ind.partition_map); - delete ind.cut_edges; - return; // do nothing - } - //else measure similarity - unsigned max_similarity = std::numeric_limits::max(); - unsigned max_similarity_idx = 0; - for( unsigned i = 0; i < m_internal_population.size(); i++) { - if(m_internal_population[i].objective >= ind.objective) { - //now measure - int diff_size = m_internal_population[i].cut_edges->size() + ind.cut_edges->size(); - std::vector output_diff(diff_size,std::numeric_limits::max()); - - set_symmetric_difference(m_internal_population[i].cut_edges->begin(), - m_internal_population[i].cut_edges->end(), - ind.cut_edges->begin(), - ind.cut_edges->end(), - output_diff.begin()); - - unsigned similarity = 0; - for( unsigned j = 0; j < output_diff.size(); j++) { - if(output_diff[j] < std::numeric_limits::max()) { - similarity++; - } else { - break; - } - } - - if( similarity < max_similarity) { - max_similarity = similarity; - max_similarity_idx = i; - } - } - } - - delete[] (m_internal_population[max_similarity_idx].partition_map); - delete m_internal_population[max_similarity_idx].cut_edges; - - m_internal_population[max_similarity_idx] = ind; - } + m_internal_population[max_similarity_idx] = ind; + } } void population::replace(Individuum & in, Individuum & out) { - //first find it: - for( unsigned i = 0; i < m_internal_population.size(); i++) { - if(m_internal_population[i].partition_map == in.partition_map) { - //found it - delete[] (m_internal_population[i].partition_map); - delete m_internal_population[i].cut_edges; - - m_internal_population[i] = out; - break; - } - } + //first find it: + for( unsigned i = 0; i < m_internal_population.size(); i++) { + if(m_internal_population[i].partition_map == in.partition_map) { + //found it + delete[] (m_internal_population[i].partition_map); + delete m_internal_population[i].cut_edges; + + m_internal_population[i] = out; + break; + } + } } -void population::combine(const PartitionConfig & partition_config, - graph_access & G, - Individuum & first_ind, - Individuum & second_ind, +void population::combine(const PartitionConfig & partition_config, + graph_access & G, + Individuum & first_ind, + Individuum & second_ind, Individuum & output_ind) { - PartitionConfig config = partition_config; - G.resizeSecondPartitionIndex(G.number_of_nodes()); - if( first_ind.objective < second_ind.objective ) { - forall_nodes(G, node) { - G.setPartitionIndex(node, first_ind.partition_map[node]); - G.setSecondPartitionIndex(node, second_ind.partition_map[node]); - - } endfor - } else { - forall_nodes(G, node) { - G.setPartitionIndex(node, second_ind.partition_map[node]); - G.setSecondPartitionIndex(node, first_ind.partition_map[node]); - } endfor - } - - config.combine = true; - config.graph_allready_partitioned = true; - config.no_new_initial_partitioning = true; - - bool coin = false; - if( partition_config.mh_enable_gal_combine ) { - coin = random_functions::nextBool(); - } - - if( coin ) { - gal_combine combine_operator; - combine_operator.perform_gal_combine( config, G); - int* partition_map = new int[G.number_of_nodes()]; - - forall_nodes(G, node) { - partition_map[node] = G.getPartitionIndex(node); - } endfor - - quality_metrics qm; - output_ind.objective = qm.objective(config, G, partition_map); - output_ind.partition_map = partition_map; - output_ind.cut_edges = new std::vector(); - - forall_nodes(G, node) { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if(partition_map[node] != partition_map[target]) { - output_ind.cut_edges->push_back(e); - } - } endfor - } endfor - } else { - createIndividuum(config, G, output_ind, true); - } - std::cout << "objective mh " << output_ind.objective << std::endl; + PartitionConfig config = partition_config; + G.resizeSecondPartitionIndex(G.number_of_nodes()); + if( first_ind.objective < second_ind.objective ) { + forall_nodes(G, node) { + G.setPartitionIndex(node, first_ind.partition_map[node]); + G.setSecondPartitionIndex(node, second_ind.partition_map[node]); + + } endfor +} else { + forall_nodes(G, node) { + G.setPartitionIndex(node, second_ind.partition_map[node]); + G.setSecondPartitionIndex(node, first_ind.partition_map[node]); + } endfor } -void population::combine_cross(const PartitionConfig & partition_config, - graph_access & G, - Individuum & first_ind, - Individuum & output_ind) { - - PartitionConfig config = partition_config; - G.resizeSecondPartitionIndex(G.number_of_nodes()); - - int lowerbound = config.k / 4; - lowerbound = std::max(2, lowerbound); - int kfactor = random_functions::nextInt(lowerbound,4*config.k); - kfactor = std::min( kfactor, (int)G.number_of_nodes()); - - if( config.mh_cross_combine_original_k ) { - MPI_Bcast(&kfactor, 1, MPI_INT, 0, m_communicator); + config.combine = true; + config.graph_allready_partitioned = true; + config.no_new_initial_partitioning = true; + + bool coin = false; + if( partition_config.mh_enable_gal_combine ) { + coin = random_functions::nextBool(); + } + + if( coin ) { + gal_combine combine_operator; + combine_operator.perform_gal_combine( config, G); + int* partition_map = new int[G.number_of_nodes()]; + + forall_nodes(G, node) { + partition_map[node] = G.getPartitionIndex(node); + } endfor + + quality_metrics qm; + output_ind.objective = qm.objective(config, G, partition_map); + output_ind.partition_map = partition_map; + output_ind.cut_edges = new std::vector(); + + forall_nodes(G, node) { + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + if(partition_map[node] != partition_map[target]) { + output_ind.cut_edges->push_back(e); } + } endfor + } endfor + } else { + createIndividuum(config, G, output_ind, true); + } + std::cout << "objective mh " << output_ind.objective << std::endl; +} - unsigned larger_imbalance = random_functions::nextInt(config.epsilon,25); - double epsilon = larger_imbalance/100.0; - - - PartitionConfig cross_config = config; - cross_config.k = kfactor; - cross_config.kaffpa_perfectly_balanced_refinement = false; - cross_config.upper_bound_partition = (1+epsilon)*ceil(partition_config.largest_graph_weight/(double)cross_config.k); - cross_config.refinement_scheduling_algorithm = REFINEMENT_SCHEDULING_ACTIVE_BLOCKS; - cross_config.combine = false; - cross_config.graph_allready_partitioned = false; - - std::ofstream ofs; - std::streambuf* backup = std::cout.rdbuf(); - ofs.open("/dev/null"); - std::cout.rdbuf(ofs.rdbuf()); - - graph_partitioner partitioner; - partitioner.perform_partitioning(cross_config, G); - - ofs.close(); - std::cout.rdbuf(backup); - - forall_nodes(G, node) { - G.setSecondPartitionIndex(node, G.getPartitionIndex(node)); - G.setPartitionIndex(node, first_ind.partition_map[node]); - } endfor - - config.combine = true; - config.graph_allready_partitioned = true; - config.no_new_initial_partitioning = true; - - createIndividuum(config, G, output_ind, true); - std::cout << "objective cross " << output_ind.objective - << " k " << kfactor - << " imbal " << larger_imbalance - << " impro " << (first_ind.objective - output_ind.objective) << std::endl; - +void population::combine_cross(PartitionConfig const& partition_config, + graph_access& G, + Individuum& first_ind, + Individuum& output_ind) { + if (partition_config.mh_cross_combine_original_k) { + auto communicator_size = 0; + ::kahip::parallel_mh::detail::check_mpi( + MPI_Comm_size(m_communicator, &communicator_size), m_communicator, + "MPI_Comm_size(evolutionary cross combine)"); + if (communicator_size <= 0) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, "evolutionary cross combine", + "MPI returned an invalid evolutionary communicator size"); + } + // Evolutionary workers enter combine_cross asynchronously. A conditional + // collective here cannot be matched by peers and therefore cannot be made + // safe without changing the algorithm's scheduling semantics. + if (communicator_size > 1) { + ::kahip::parallel_mh::detail::abort_evolutionary_collective( + m_communicator, "evolutionary cross combine", + "original-k cross combine is incompatible with asynchronous " + "multi-rank entry"); + } + } + + PartitionConfig config = partition_config; + G.resizeSecondPartitionIndex(G.number_of_nodes()); + + int lowerbound = config.k / 4; + lowerbound = std::max(2, lowerbound); + int kfactor = random_functions::nextInt(lowerbound, 4 * config.k); + kfactor = std::min(kfactor, (int)G.number_of_nodes()); + + unsigned larger_imbalance = random_functions::nextInt(config.epsilon, 25); + double epsilon = larger_imbalance / 100.0; + + PartitionConfig cross_config = config; + cross_config.k = kfactor; + cross_config.kaffpa_perfectly_balanced_refinement = false; + cross_config.upper_bound_partition = + (1 + epsilon) * + ceil(partition_config.largest_graph_weight / (double)cross_config.k); + cross_config.refinement_scheduling_algorithm = + REFINEMENT_SCHEDULING_ACTIVE_BLOCKS; + cross_config.combine = false; + cross_config.graph_allready_partitioned = false; + + graph_partitioner partitioner; + { + auto suppression = scoped_output_suppression{}; + partitioner.perform_partitioning(cross_config, G); + } + + forall_nodes(G, node) { + G.setSecondPartitionIndex(node, G.getPartitionIndex(node)); + G.setPartitionIndex(node, first_ind.partition_map[node]); + } endfor + + config.combine = true; + config.graph_allready_partitioned = true; + config.no_new_initial_partitioning = true; + + createIndividuum(config, G, output_ind, true); + std::cout << "objective cross " << output_ind.objective + << " k " << kfactor + << " imbal " << larger_imbalance + << " impro " << (first_ind.objective - output_ind.objective) << std::endl; } void population::mutate_random( const PartitionConfig & partition_config, graph_access & G, Individuum & first_ind) { - int number = random_functions::nextInt(0,5); + int number = random_functions::nextInt(0,5); - PartitionConfig config = partition_config; - config.combine = false; - config.graph_allready_partitioned = true; - get_random_individuum(first_ind); + PartitionConfig config = partition_config; + config.combine = false; + config.graph_allready_partitioned = true; + get_random_individuum(first_ind); - if(number < 5) { - forall_nodes(G, node) { - G.setPartitionIndex(node, first_ind.partition_map[node]); - } endfor + if(number < 5) { + forall_nodes(G, node) { + G.setPartitionIndex(node, first_ind.partition_map[node]); + } endfor - config.no_new_initial_partitioning = true; - createIndividuum( config, G, first_ind, true); + config.no_new_initial_partitioning = true; + createIndividuum( config, G, first_ind, true); - } else { - forall_nodes(G, node) { - G.setPartitionIndex(node, first_ind.partition_map[node]); - } endfor + } else { + forall_nodes(G, node) { + G.setPartitionIndex(node, first_ind.partition_map[node]); + } endfor - config.graph_allready_partitioned = false; - createIndividuum( config, G, first_ind, true); - } + config.graph_allready_partitioned = false; + createIndividuum( config, G, first_ind, true); + } } void population::extinction( ) { - for( unsigned i = 0; i < m_internal_population.size(); i++) { - delete[] m_internal_population[i].partition_map; - delete m_internal_population[i].cut_edges; - } + for( unsigned i = 0; i < m_internal_population.size(); i++) { + delete[] m_internal_population[i].partition_map; + delete m_internal_population[i].cut_edges; + } - m_internal_population.clear(); - m_internal_population.resize(0); + m_internal_population.clear(); + m_internal_population.resize(0); } void population::get_two_random_individuals(Individuum & first, Individuum & second) { - int first_idx = random_functions::nextInt(0, m_internal_population.size()-1); - first = m_internal_population[first_idx]; + int first_idx = random_functions::nextInt(0, m_internal_population.size()-1); + first = m_internal_population[first_idx]; - int second_idx = random_functions::nextInt(0, m_internal_population.size()-1); - while( first_idx == second_idx ) { - second_idx = random_functions::nextInt(0, m_internal_population.size()-1); - } + int second_idx = random_functions::nextInt(0, m_internal_population.size()-1); + while( first_idx == second_idx ) { + second_idx = random_functions::nextInt(0, m_internal_population.size()-1); + } - second = m_internal_population[second_idx]; + second = m_internal_population[second_idx]; } void population::get_one_individual_tournament(Individuum & first) { - Individuum one, two; - get_two_random_individuals(one, two); - first = one.objective < two.objective ? one : two; + Individuum one, two; + get_two_random_individuals(one, two); + first = one.objective < two.objective ? one : two; } void population::get_two_individuals_tournament(Individuum & first, Individuum & second) { - Individuum one, two; - get_two_random_individuals(one, two); - first = one.objective < two.objective? one : two; + Individuum one, two; + get_two_random_individuals(one, two); + first = one.objective < two.objective? one : two; - get_two_random_individuals(one, two); - second = one.objective < two.objective ? one : two; + get_two_random_individuals(one, two); + second = one.objective < two.objective ? one : two; - if( first.objective == second.objective) { - second = one.objective >= two.objective? one : two; - } + if( first.objective == second.objective) { + second = one.objective >= two.objective? one : two; + } } void population::get_random_individuum(Individuum & ind) { - int idx = random_functions::nextInt(0, m_internal_population.size()-1); - ind = m_internal_population[idx]; + int idx = random_functions::nextInt(0, m_internal_population.size()-1); + ind = m_internal_population[idx]; } void population::get_best_individuum(Individuum & ind) { - EdgeWeight min_objective = std::numeric_limits::max(); - unsigned idx = 0; - - for( unsigned i = 0; i < m_internal_population.size(); i++) { - if((EdgeWeight)m_internal_population[i].objective < min_objective) { - min_objective = m_internal_population[i].objective; - idx = i; - } - } + EdgeWeight min_objective = std::numeric_limits::max(); + unsigned idx = 0; - ind = m_internal_population[idx]; + for( unsigned i = 0; i < m_internal_population.size(); i++) { + if((EdgeWeight)m_internal_population[i].objective < min_objective) { + min_objective = m_internal_population[i].objective; + idx = i; + } + } + + ind = m_internal_population[idx]; } bool population::is_full() { - return m_internal_population.size() == m_population_size; + return m_internal_population.size() == m_population_size; } void population::apply_fittest( graph_access & G, EdgeWeight & objective ) { - EdgeWeight min_objective = std::numeric_limits::max(); - double best_balance = std::numeric_limits::max(); - unsigned idx = 0; - - quality_metrics qm; - for( unsigned i = 0; i < m_internal_population.size(); i++) { - forall_nodes(G, node) { - G.setPartitionIndex(node, m_internal_population[i].partition_map[node]); - } endfor - double cur_balance = qm.balance(G); - if((EdgeWeight)m_internal_population[i].objective < min_objective - || ((EdgeWeight)m_internal_population[i].objective == min_objective && cur_balance < best_balance)) { - min_objective = m_internal_population[i].objective; - idx = i; - best_balance = cur_balance; - } - } + EdgeWeight min_objective = std::numeric_limits::max(); + double best_balance = std::numeric_limits::max(); + unsigned idx = 0; + + quality_metrics qm; + for( unsigned i = 0; i < m_internal_population.size(); i++) { + forall_nodes(G, node) { + G.setPartitionIndex(node, m_internal_population[i].partition_map[node]); + } endfor + double cur_balance = qm.balance(G); + if((EdgeWeight)m_internal_population[i].objective < min_objective +|| ((EdgeWeight)m_internal_population[i].objective == min_objective && cur_balance < best_balance)) { + min_objective = m_internal_population[i].objective; + idx = i; + best_balance = cur_balance; +} + } - forall_nodes(G, node) { - G.setPartitionIndex(node, m_internal_population[idx].partition_map[node]); - } endfor + forall_nodes(G, node) { + G.setPartitionIndex(node, m_internal_population[idx].partition_map[node]); + } endfor - objective = min_objective; + objective = min_objective; } void population::print() { - int rank; - MPI_Comm_rank( m_communicator, &rank); - - std::cout << "rank " << rank << " fingerprint "; + auto rank = -1; + ::kahip::parallel_mh::detail::check_mpi( + MPI_Comm_rank(m_communicator, &rank), m_communicator, + "MPI_Comm_rank(evolutionary population print)"); + + std::cout << "rank " << rank << " fingerprint "; - for( unsigned i = 0; i < m_internal_population.size(); i++) { - std::cout << m_internal_population[i].objective << " "; - } + for( unsigned i = 0; i < m_internal_population.size(); i++) { + std::cout << m_internal_population[i].objective << " "; + } - std::cout << std::endl; + std::cout << std::endl; } void population::write_log(std::string & filename) { - std::ofstream f(filename.c_str()); - f << m_filebuffer_string.str(); - f.close(); + std::ofstream f(filename.c_str()); + f << m_filebuffer_string.str(); + f.close(); +} } - diff --git a/parallel/modified_kahip/lib/parallel_mh/population.h b/parallel/modified_kahip/lib/parallel_mh/population.h index 37e27db9..3355c1d9 100644 --- a/parallel/modified_kahip/lib/parallel_mh/population.h +++ b/parallel/modified_kahip/lib/parallel_mh/population.h @@ -14,11 +14,11 @@ #include "data_structure/graph_access.h" #include "partition_config.h" #include "timer.h" - +namespace kahip::modified { struct Individuum { - int* partition_map; - EdgeWeight objective; - std::vector* cut_edges; //sorted + int* partition_map = nullptr; + EdgeWeight objective = 0; + std::vector* cut_edges = nullptr; //sorted }; struct ENC { @@ -26,77 +26,77 @@ struct ENC { }; class population { - public: - population( MPI_Comm comm, const PartitionConfig & config ); - virtual ~population(); +public: + population( MPI_Comm comm, const PartitionConfig & config ); + virtual ~population(); - void createIndividuum(const PartitionConfig & config, - graph_access & G, - Individuum & ind, - bool output); + void createIndividuum(const PartitionConfig & config, + graph_access & G, + Individuum & ind, + bool output); - void combine(const PartitionConfig & config, - graph_access & G, - Individuum & first_ind, - Individuum & second_ind, - Individuum & output_ind); + void combine(const PartitionConfig & config, + graph_access & G, + Individuum & first_ind, + Individuum & second_ind, + Individuum & output_ind); - void combine_cross(const PartitionConfig & partition_config, - graph_access & G, - Individuum & first_ind, - Individuum & output_ind); + void combine_cross(const PartitionConfig & partition_config, + graph_access & G, + Individuum & first_ind, + Individuum & output_ind); - void mutate_random(const PartitionConfig & partition_config, - graph_access & G, - Individuum & first_ind); + void mutate_random(const PartitionConfig & partition_config, + graph_access & G, + Individuum & first_ind); - void insert(graph_access & G, Individuum & ind); + void insert(graph_access & G, Individuum & ind); - void set_pool_size(int size); + void set_pool_size(int size); - void extinction(); + void extinction(); - void get_two_random_individuals(Individuum & first, Individuum & second); - - void get_one_individual_tournament(Individuum & first); + void get_two_random_individuals(Individuum & first, Individuum & second); - void get_two_individuals_tournament(Individuum & first, Individuum & second); + void get_one_individual_tournament(Individuum & first); - void replace(Individuum & in, Individuum & out); + void get_two_individuals_tournament(Individuum & first, Individuum & second); - void get_random_individuum(Individuum & ind); + void replace(Individuum & in, Individuum & out); - void get_best_individuum(Individuum & ind); + void get_random_individuum(Individuum & ind); - bool is_full(); + void get_best_individuum(Individuum & ind); - void apply_fittest( graph_access & G, EdgeWeight & objective); + bool is_full(); - unsigned size() { return m_internal_population.size(); } - - void print(); + void apply_fittest( graph_access & G, EdgeWeight & objective); - void write_log(std::string & filename); + unsigned size() { return m_internal_population.size(); } + void print(); - private: + void write_log(std::string & filename); - unsigned m_no_partition_calls; - unsigned m_population_size; - std::vector m_internal_population; - std::vector< std::vector< unsigned int > > m_vertex_ENCs; - std::vector< ENC > m_ENCs; - int m_num_NCs; - int m_num_NCs_computed; - int m_num_ENCs; - int m_time_stamp; +private: - MPI_Comm m_communicator; + unsigned m_no_partition_calls = 0; + unsigned m_population_size = 0; + std::vector m_internal_population; + std::vector< std::vector< unsigned int > > m_vertex_ENCs; + std::vector< ENC > m_ENCs; - std::stringstream m_filebuffer_string; - timer m_global_timer; -}; + int m_num_NCs = 0; + int m_num_NCs_computed = 0; + int m_num_ENCs = 0; + int m_time_stamp = 0; + MPI_Comm m_communicator = MPI_COMM_NULL; + + std::stringstream m_filebuffer_string; + timer m_global_timer; +}; +} #endif /* end of include guard: POPULATION_AEFH46G6 */ diff --git a/parallel/modified_kahip/lib/partition/coarsening/clustering/node_ordering.cpp b/parallel/modified_kahip/lib/partition/coarsening/clustering/node_ordering.cpp index 5c0c6ce2..9af009f3 100644 --- a/parallel/modified_kahip/lib/partition/coarsening/clustering/node_ordering.cpp +++ b/parallel/modified_kahip/lib/partition/coarsening/clustering/node_ordering.cpp @@ -7,7 +7,7 @@ #include "node_ordering.h" - +namespace kahip::modified { node_ordering::node_ordering() { } @@ -15,4 +15,4 @@ node_ordering::node_ordering() { node_ordering::~node_ordering() { } - +} diff --git a/parallel/modified_kahip/lib/partition/coarsening/clustering/node_ordering.h b/parallel/modified_kahip/lib/partition/coarsening/clustering/node_ordering.h index b87d7d2a..7d989ed6 100644 --- a/parallel/modified_kahip/lib/partition/coarsening/clustering/node_ordering.h +++ b/parallel/modified_kahip/lib/partition/coarsening/clustering/node_ordering.h @@ -14,7 +14,7 @@ #include "partition_config.h" #include "data_structure/graph_access.h" #include "tools/random_functions.h" - +namespace kahip::modified { class node_ordering { public: node_ordering(); @@ -28,25 +28,25 @@ class node_ordering { switch( config.node_ordering ) { case RANDOM_NODEORDERING: order_nodes_random(config, G, ordered_nodes); - break; + break; case DEGREE_NODEORDERING: order_nodes_degree(config, G, ordered_nodes); - break; - } + break; + } } - void order_nodes_random(const PartitionConfig & config, graph_access & G, std::vector< NodeID > & ordered_nodes) { + void order_nodes_random(const PartitionConfig & config, graph_access & G, std::vector< NodeID > & ordered_nodes) { random_functions::permutate_vector_fast(ordered_nodes, false); } - void order_nodes_degree(const PartitionConfig & config, graph_access & G, std::vector< NodeID > & ordered_nodes) { - std::sort( ordered_nodes.begin(), ordered_nodes.end(), + void order_nodes_degree(const PartitionConfig & config, graph_access & G, std::vector< NodeID > & ordered_nodes) { + std::sort( ordered_nodes.begin(), ordered_nodes.end(), [&]( const NodeID & lhs, const NodeID & rhs) -> bool { return (G.getNodeDegree(lhs) < G.getNodeDegree(rhs)); }); } - }; - +}; +} #endif /* end of include guard: NODE_ORDERING_HM1YMLB1 */ diff --git a/parallel/modified_kahip/lib/partition/coarsening/clustering/size_constraint_label_propagation.cpp b/parallel/modified_kahip/lib/partition/coarsening/clustering/size_constraint_label_propagation.cpp index b38195b6..a7c3000b 100644 --- a/parallel/modified_kahip/lib/partition/coarsening/clustering/size_constraint_label_propagation.cpp +++ b/parallel/modified_kahip/lib/partition/coarsening/clustering/size_constraint_label_propagation.cpp @@ -20,7 +20,7 @@ #include "io/graph_io.h" #include "size_constraint_label_propagation.h" - +namespace kahip::modified { size_constraint_label_propagation::size_constraint_label_propagation() { } @@ -35,202 +35,202 @@ void size_constraint_label_propagation::match(const PartitionConfig & partition_ CoarseMapping & coarse_mapping, NodeID & no_of_coarse_vertices, NodePermutationMap & permutation) { - permutation.resize(G.number_of_nodes()); - coarse_mapping.resize(G.number_of_nodes()); - no_of_coarse_vertices = 0; - - if ( partition_config.ensemble_clusterings ) { - ensemble_clusterings(partition_config, G, _matching, coarse_mapping, no_of_coarse_vertices, permutation); - } else { - match_internal(partition_config, G, _matching, coarse_mapping, no_of_coarse_vertices, permutation); - } + permutation.resize(G.number_of_nodes()); + coarse_mapping.resize(G.number_of_nodes()); + no_of_coarse_vertices = 0; + + if ( partition_config.ensemble_clusterings ) { + ensemble_clusterings(partition_config, G, _matching, coarse_mapping, no_of_coarse_vertices, permutation); + } else { + match_internal(partition_config, G, _matching, coarse_mapping, no_of_coarse_vertices, permutation); + } } -void size_constraint_label_propagation::match_internal(const PartitionConfig & partition_config, - graph_access & G, - Matching & _matching, - CoarseMapping & coarse_mapping, +void size_constraint_label_propagation::match_internal(const PartitionConfig & partition_config, + graph_access & G, + Matching & _matching, + CoarseMapping & coarse_mapping, NodeID & no_of_coarse_vertices, NodePermutationMap & permutation) { - std::vector cluster_id(G.number_of_nodes()); - NodeWeight block_upperbound = ceil(partition_config.upper_bound_partition/(double)partition_config.cluster_coarsening_factor); + std::vector cluster_id(G.number_of_nodes()); + NodeWeight block_upperbound = ceil(partition_config.upper_bound_partition/(double)partition_config.cluster_coarsening_factor); - label_propagation( partition_config, G, block_upperbound, cluster_id, no_of_coarse_vertices); - create_coarsemapping( partition_config, G, cluster_id, coarse_mapping); + label_propagation( partition_config, G, block_upperbound, cluster_id, no_of_coarse_vertices); + create_coarsemapping( partition_config, G, cluster_id, coarse_mapping); } -void size_constraint_label_propagation::ensemble_two_clusterings( graph_access & G, - std::vector & lhs, - std::vector & rhs, +void size_constraint_label_propagation::ensemble_two_clusterings( graph_access & G, + std::vector & lhs, + std::vector & rhs, std::vector< NodeID > & output, NodeID & no_of_coarse_vertices) { - hash_ensemble new_mapping; - no_of_coarse_vertices = 0; - for( NodeID node = 0; node < lhs.size(); node++) { - ensemble_pair cur_pair; - cur_pair.lhs = lhs[node]; - cur_pair.rhs = rhs[node]; - cur_pair.n = G.number_of_nodes(); + hash_ensemble new_mapping; + no_of_coarse_vertices = 0; + for( NodeID node = 0; node < lhs.size(); node++) { + ensemble_pair cur_pair; + cur_pair.lhs = lhs[node]; + cur_pair.rhs = rhs[node]; + cur_pair.n = G.number_of_nodes(); - if(new_mapping.find(cur_pair) == new_mapping.end() ) { - new_mapping[cur_pair].mapping = no_of_coarse_vertices; - no_of_coarse_vertices++; - } + if(new_mapping.find(cur_pair) == new_mapping.end() ) { + new_mapping[cur_pair].mapping = no_of_coarse_vertices; + no_of_coarse_vertices++; + } - output[node] = new_mapping[cur_pair].mapping; - } + output[node] = new_mapping[cur_pair].mapping; + } - no_of_coarse_vertices = new_mapping.size(); + no_of_coarse_vertices = new_mapping.size(); } -void size_constraint_label_propagation::ensemble_clusterings(const PartitionConfig & partition_config, - graph_access & G, - Matching & _matching, - CoarseMapping & coarse_mapping, +void size_constraint_label_propagation::ensemble_clusterings(const PartitionConfig & partition_config, + graph_access & G, + Matching & _matching, + CoarseMapping & coarse_mapping, NodeID & no_of_coarse_vertices, NodePermutationMap & permutation) { - int runs = partition_config.number_of_clusterings; - std::vector< NodeID > cur_cluster(G.number_of_nodes(), 0); - std::vector< NodeID > ensemble_cluster(G.number_of_nodes(),0); - - std::cerr << "G.number_of_nodes " << G.number_of_nodes() << std::endl; - int new_cf = partition_config.cluster_coarsening_factor; - for( int i = 0; i < runs; i++) { - PartitionConfig config = partition_config; - - std::cerr << "config " << config.k << std::endl; - config.cluster_coarsening_factor = new_cf; - - NodeID cur_no_blocks = 0; - label_propagation(config, G, cur_cluster, cur_no_blocks); - - if( i != 0 ) { - ensemble_two_clusterings(G, cur_cluster, ensemble_cluster, ensemble_cluster, no_of_coarse_vertices); - } else { - forall_nodes(G, node) { - ensemble_cluster[node] = cur_cluster[node]; - } endfor - - no_of_coarse_vertices = cur_no_blocks; - } - new_cf = random_functions::nextInt(10, 30); - } + int runs = partition_config.number_of_clusterings; + std::vector< NodeID > cur_cluster(G.number_of_nodes(), 0); + std::vector< NodeID > ensemble_cluster(G.number_of_nodes(),0); + + std::cerr << "G.number_of_nodes " << G.number_of_nodes() << std::endl; + int new_cf = partition_config.cluster_coarsening_factor; + for( int i = 0; i < runs; i++) { + PartitionConfig config = partition_config; + + std::cerr << "config " << config.k << std::endl; + config.cluster_coarsening_factor = new_cf; - create_coarsemapping( partition_config, G, ensemble_cluster, coarse_mapping); + NodeID cur_no_blocks = 0; + label_propagation(config, G, cur_cluster, cur_no_blocks); + + if( i != 0 ) { + ensemble_two_clusterings(G, cur_cluster, ensemble_cluster, ensemble_cluster, no_of_coarse_vertices); + } else { + forall_nodes(G, node) { + ensemble_cluster[node] = cur_cluster[node]; + } endfor + + no_of_coarse_vertices = cur_no_blocks; + } + new_cf = random_functions::nextInt(10, 30); + } + + create_coarsemapping( partition_config, G, ensemble_cluster, coarse_mapping); } -void size_constraint_label_propagation::label_propagation(const PartitionConfig & partition_config, - graph_access & G, - std::vector & cluster_id, +void size_constraint_label_propagation::label_propagation(const PartitionConfig & partition_config, + graph_access & G, + std::vector & cluster_id, NodeID & no_of_blocks ) { - NodeWeight block_upperbound = ceil(partition_config.upper_bound_partition/(double)partition_config.cluster_coarsening_factor); + NodeWeight block_upperbound = ceil(partition_config.upper_bound_partition/(double)partition_config.cluster_coarsening_factor); - label_propagation( partition_config, G, block_upperbound, cluster_id, no_of_blocks); + label_propagation( partition_config, G, block_upperbound, cluster_id, no_of_blocks); } -void size_constraint_label_propagation::label_propagation(const PartitionConfig & partition_config, - graph_access & G, +void size_constraint_label_propagation::label_propagation(const PartitionConfig & partition_config, + graph_access & G, const NodeWeight & block_upperbound, - std::vector & cluster_id, + std::vector & cluster_id, NodeID & no_of_blocks) { - // in this case the _matching paramter is not used - // coarse_mappng stores cluster id and the mapping (it is identical) - std::vector hash_map(G.number_of_nodes(),0); - std::vector permutation(G.number_of_nodes()); - std::vector cluster_sizes(G.number_of_nodes()); - cluster_id.resize(G.number_of_nodes()); - - forall_nodes(G, node) { - cluster_sizes[node] = G.getNodeWeight(node); - cluster_id[node] = node; - } endfor - - node_ordering n_ordering; - n_ordering.order_nodes(partition_config, G, permutation); - - for( int j = 0; j < partition_config.label_iterations; j++) { - unsigned int change_counter = 0; - forall_nodes(G, i) { - NodeID node = permutation[i]; - //now move the node to the cluster that is most common in the neighborhood - - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - hash_map[cluster_id[target]]+=G.getEdgeWeight(e); - } endfor - - //second sweep for finding max and resetting array - PartitionID max_block = cluster_id[node]; - PartitionID my_block = cluster_id[node]; - - PartitionID max_value = 0; - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - PartitionID cur_block = cluster_id[target]; - PartitionID cur_value = hash_map[cur_block]; - if((cur_value > max_value || (cur_value == max_value && random_functions::nextBool())) - && (cluster_sizes[cur_block] + G.getNodeWeight(node) < block_upperbound || cur_block == my_block) - && (!partition_config.graph_allready_partitioned || G.getPartitionIndex(node) == G.getPartitionIndex(target)) - && (!partition_config.combine || G.getSecondPartitionIndex(node) == G.getSecondPartitionIndex(target))) - { - max_value = cur_value; - max_block = cur_block; - } - - hash_map[cur_block] = 0; - } endfor - - cluster_sizes[cluster_id[node]] -= G.getNodeWeight(node); - cluster_sizes[max_block] += G.getNodeWeight(node); - change_counter += (cluster_id[node] != max_block); - cluster_id[node] = max_block; - } endfor + // in this case the _matching paramter is not used + // coarse_mappng stores cluster id and the mapping (it is identical) + std::vector hash_map(G.number_of_nodes(),0); + std::vector permutation(G.number_of_nodes()); + std::vector cluster_sizes(G.number_of_nodes()); + cluster_id.resize(G.number_of_nodes()); + + forall_nodes(G, node) { + cluster_sizes[node] = G.getNodeWeight(node); + cluster_id[node] = node; + } endfor + + node_ordering n_ordering; + n_ordering.order_nodes(partition_config, G, permutation); + + for( int j = 0; j < partition_config.label_iterations; j++) { + unsigned int change_counter = 0; + forall_nodes(G, i) { + NodeID node = permutation[i]; + //now move the node to the cluster that is most common in the neighborhood + + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + hash_map[cluster_id[target]]+=G.getEdgeWeight(e); + } endfor + + //second sweep for finding max and resetting array + PartitionID max_block = cluster_id[node]; + PartitionID my_block = cluster_id[node]; + + PartitionID max_value = 0; + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + PartitionID cur_block = cluster_id[target]; + PartitionID cur_value = hash_map[cur_block]; + if((cur_value > max_value || (cur_value == max_value && random_functions::nextBool())) + && (cluster_sizes[cur_block] + G.getNodeWeight(node) < block_upperbound || cur_block == my_block) + && (!partition_config.graph_allready_partitioned || G.getPartitionIndex(node) == G.getPartitionIndex(target)) + && (!partition_config.combine || G.getSecondPartitionIndex(node) == G.getSecondPartitionIndex(target))) + { + max_value = cur_value; + max_block = cur_block; } - remap_cluster_ids( partition_config, G, cluster_id, no_of_blocks); + hash_map[cur_block] = 0; + } endfor + + cluster_sizes[cluster_id[node]] -= G.getNodeWeight(node); + cluster_sizes[max_block] += G.getNodeWeight(node); + change_counter += (cluster_id[node] != max_block); + cluster_id[node] = max_block; + } endfor +} + + remap_cluster_ids( partition_config, G, cluster_id, no_of_blocks); } -void size_constraint_label_propagation::create_coarsemapping(const PartitionConfig & partition_config, +void size_constraint_label_propagation::create_coarsemapping(const PartitionConfig & partition_config, graph_access & G, std::vector & cluster_id, CoarseMapping & coarse_mapping) { - forall_nodes(G, node) { - coarse_mapping[node] = cluster_id[node]; - } endfor + forall_nodes(G, node) { + coarse_mapping[node] = cluster_id[node]; + } endfor } -void size_constraint_label_propagation::remap_cluster_ids(const PartitionConfig & partition_config, +void size_constraint_label_propagation::remap_cluster_ids(const PartitionConfig & partition_config, graph_access & G, std::vector & cluster_id, NodeID & no_of_coarse_vertices, bool apply_to_graph) { - PartitionID cur_no_clusters = 0; - std::unordered_map remap; - forall_nodes(G, node) { - PartitionID cur_cluster = cluster_id[node]; - //check wether we already had that - if( remap.find( cur_cluster ) == remap.end() ) { - remap[cur_cluster] = cur_no_clusters++; - } - - cluster_id[node] = remap[cur_cluster]; - } endfor - - if( apply_to_graph ) { - forall_nodes(G, node) { - G.setPartitionIndex(node, cluster_id[node]); - } endfor - G.set_partition_count(cur_no_clusters); - } - - no_of_coarse_vertices = cur_no_clusters; + PartitionID cur_no_clusters = 0; + std::unordered_map remap; + forall_nodes(G, node) { + PartitionID cur_cluster = cluster_id[node]; + //check wether we already had that + if( remap.find( cur_cluster ) == remap.end() ) { + remap[cur_cluster] = cur_no_clusters++; + } + + cluster_id[node] = remap[cur_cluster]; + } endfor + + if( apply_to_graph ) { + forall_nodes(G, node) { + G.setPartitionIndex(node, cluster_id[node]); + } endfor + G.set_partition_count(cur_no_clusters); + } + + no_of_coarse_vertices = cur_no_clusters; +} } - diff --git a/parallel/modified_kahip/lib/partition/coarsening/clustering/size_constraint_label_propagation.h b/parallel/modified_kahip/lib/partition/coarsening/clustering/size_constraint_label_propagation.h index afea2d79..0e46df8a 100644 --- a/parallel/modified_kahip/lib/partition/coarsening/clustering/size_constraint_label_propagation.h +++ b/parallel/modified_kahip/lib/partition/coarsening/clustering/size_constraint_label_propagation.h @@ -10,96 +10,96 @@ #include #include "../matching/matching.h" - +namespace kahip::modified { struct ensemble_pair { - PartitionID n; // number of nodes in the graph - PartitionID lhs; - PartitionID rhs; + PartitionID n; // number of nodes in the graph + PartitionID lhs; + PartitionID rhs; }; struct compare_ensemble_pair { - bool operator()(const ensemble_pair pair_a, const ensemble_pair pair_b) const { - bool eq = (pair_a.lhs == pair_b.lhs && pair_a.rhs == pair_b.rhs); - return eq; - } + bool operator()(const ensemble_pair pair_a, const ensemble_pair pair_b) const { + bool eq = (pair_a.lhs == pair_b.lhs && pair_a.rhs == pair_b.rhs); + return eq; + } }; struct hash_ensemble_pair{ - size_t operator()(const ensemble_pair pair) const { - return pair.lhs*pair.n + pair.rhs; - } + size_t operator()(const ensemble_pair pair) const { + return pair.lhs*pair.n + pair.rhs; + } }; struct data_ensemble_pair { - NodeID mapping; + NodeID mapping; - data_ensemble_pair() { - mapping = 0; - } + data_ensemble_pair() { + mapping = 0; + } }; -typedef std::unordered_map hash_ensemble; class size_constraint_label_propagation : public matching { - public: - size_constraint_label_propagation(); - virtual ~size_constraint_label_propagation(); - - void match(const PartitionConfig & config, - graph_access & G, - Matching & _matching, - CoarseMapping & coarse_mapping, - NodeID & no_of_coarse_vertices, - NodePermutationMap & permutation); - - - void ensemble_clusterings(const PartitionConfig & config, - graph_access & G, - Matching & _matching, - CoarseMapping & coarse_mapping, - NodeID & no_of_coarse_vertices, - NodePermutationMap & permutation); - - void ensemble_two_clusterings( graph_access & G, - std::vector & lhs, - std::vector & rhs, - std::vector< NodeID > & output, - NodeID & no_of_coarse_vertices); - - void match_internal(const PartitionConfig & config, - graph_access & G, - Matching & _matching, - CoarseMapping & coarse_mapping, - NodeID & no_of_coarse_vertices, - NodePermutationMap & permutation); - - void remap_cluster_ids(const PartitionConfig & partition_config, - graph_access & G, - std::vector & cluster_id, - NodeID & no_of_coarse_vertices, - bool apply_to_graph = false); - - void create_coarsemapping(const PartitionConfig & partition_config, - graph_access & G, - std::vector & cluster_id, - CoarseMapping & coarse_mapping); - - void label_propagation(const PartitionConfig & partition_config, - graph_access & G, - const NodeWeight & block_upperbound, - std::vector & cluster_id, // output paramter - NodeID & number_of_blocks); // output parameter - - void label_propagation(const PartitionConfig & partition_config, - graph_access & G, - std::vector & cluster_id, - NodeID & number_of_blocks ); +public: + size_constraint_label_propagation(); + virtual ~size_constraint_label_propagation(); + + void match(const PartitionConfig & config, + graph_access & G, + Matching & _matching, + CoarseMapping & coarse_mapping, + NodeID & no_of_coarse_vertices, + NodePermutationMap & permutation); + + + void ensemble_clusterings(const PartitionConfig & config, + graph_access & G, + Matching & _matching, + CoarseMapping & coarse_mapping, + NodeID & no_of_coarse_vertices, + NodePermutationMap & permutation); + + void ensemble_two_clusterings( graph_access & G, + std::vector & lhs, + std::vector & rhs, + std::vector< NodeID > & output, + NodeID & no_of_coarse_vertices); + + void match_internal(const PartitionConfig & config, + graph_access & G, + Matching & _matching, + CoarseMapping & coarse_mapping, + NodeID & no_of_coarse_vertices, + NodePermutationMap & permutation); + + void remap_cluster_ids(const PartitionConfig & partition_config, + graph_access & G, + std::vector & cluster_id, + NodeID & no_of_coarse_vertices, + bool apply_to_graph = false); + + void create_coarsemapping(const PartitionConfig & partition_config, + graph_access & G, + std::vector & cluster_id, + CoarseMapping & coarse_mapping); + + void label_propagation(const PartitionConfig & partition_config, + graph_access & G, + const NodeWeight & block_upperbound, + std::vector & cluster_id, // output paramter + NodeID & number_of_blocks); // output parameter + + void label_propagation(const PartitionConfig & partition_config, + graph_access & G, + std::vector & cluster_id, + NodeID & number_of_blocks ); }; - +} #endif /* end of include guard: SIZE_CONSTRAINT_LABEL_PROPAGATION_7SVLBKKT */ diff --git a/parallel/modified_kahip/lib/partition/coarsening/coarsening.cpp b/parallel/modified_kahip/lib/partition/coarsening/coarsening.cpp index fc0d8d91..2b3e8777 100644 --- a/parallel/modified_kahip/lib/partition/coarsening/coarsening.cpp +++ b/parallel/modified_kahip/lib/partition/coarsening/coarsening.cpp @@ -18,7 +18,7 @@ #include "matching/gpa/gpa_matching.h" #include "matching/random_matching.h" #include "stop_rules/stop_rules.h" - +namespace kahip::modified { coarsening::coarsening() { } @@ -29,68 +29,68 @@ coarsening::~coarsening() { void coarsening::perform_coarsening(const PartitionConfig & partition_config, graph_access & G, graph_hierarchy & hierarchy) { - NodeID no_of_coarser_vertices = G.number_of_nodes(); - NodeID no_of_finer_vertices = G.number_of_nodes(); - - edge_ratings rating(partition_config); - CoarseMapping* coarse_mapping = NULL; - - graph_access* finer = &G; - matching* edge_matcher = NULL; - contraction* contracter = new contraction(); - PartitionConfig copy_of_partition_config = partition_config; - - stop_rule* coarsening_stop_rule = NULL; - if(partition_config.stop_rule == STOP_RULE_SIMPLE) { - coarsening_stop_rule = new simple_stop_rule(copy_of_partition_config, G.number_of_nodes()); - } else if(partition_config.stop_rule == STOP_RULE_MULTIPLE_K) { - coarsening_stop_rule = new multiple_k_stop_rule(copy_of_partition_config, G.number_of_nodes()); - } else { - coarsening_stop_rule = new strong_stop_rule(copy_of_partition_config, G.number_of_nodes()); - } - - coarsening_configurator coarsening_config; - - unsigned int level = 0; - bool contraction_stop = false; - do { - graph_access* coarser = new graph_access(); - coarse_mapping = new CoarseMapping(); - Matching edge_matching; - NodePermutationMap permutation; - - coarsening_config.configure_coarsening(copy_of_partition_config, &edge_matcher, level); - rating.rate(*finer, level); - - edge_matcher->match(copy_of_partition_config, *finer, edge_matching, - *coarse_mapping, no_of_coarser_vertices, permutation); - - delete edge_matcher; - - if(partition_config.graph_allready_partitioned) { - contracter->contract_partitioned(copy_of_partition_config, *finer, *coarser, edge_matching, - *coarse_mapping, no_of_coarser_vertices, permutation); - } else { - contracter->contract(copy_of_partition_config, *finer, *coarser, edge_matching, - *coarse_mapping, no_of_coarser_vertices, permutation); - } - - hierarchy.push_back(finer, coarse_mapping); - contraction_stop = coarsening_stop_rule->stop(no_of_finer_vertices, no_of_coarser_vertices); - - no_of_finer_vertices = no_of_coarser_vertices; - PRINT(std::cout << "no of coarser vertices " << no_of_coarser_vertices - << " and no of edges " << coarser->number_of_edges() << std::endl;) - - finer = coarser; - - level++; - } while( contraction_stop ); - - hierarchy.push_back(finer, NULL); // append the last created level - - delete contracter; - delete coarsening_stop_rule; -} + NodeID no_of_coarser_vertices = G.number_of_nodes(); + NodeID no_of_finer_vertices = G.number_of_nodes(); + + edge_ratings rating(partition_config); + CoarseMapping* coarse_mapping = NULL; + + graph_access* finer = &G; + matching* edge_matcher = NULL; + contraction* contracter = new contraction(); + PartitionConfig copy_of_partition_config = partition_config; + + stop_rule* coarsening_stop_rule = NULL; + if(partition_config.stop_rule == STOP_RULE_SIMPLE) { + coarsening_stop_rule = new simple_stop_rule(copy_of_partition_config, G.number_of_nodes()); + } else if(partition_config.stop_rule == STOP_RULE_MULTIPLE_K) { + coarsening_stop_rule = new multiple_k_stop_rule(copy_of_partition_config, G.number_of_nodes()); + } else { + coarsening_stop_rule = new strong_stop_rule(copy_of_partition_config, G.number_of_nodes()); + } + + coarsening_configurator coarsening_config; + + unsigned int level = 0; + bool contraction_stop = false; + do { + graph_access* coarser = new graph_access(); + coarse_mapping = new CoarseMapping(); + Matching edge_matching; + NodePermutationMap permutation; + + coarsening_config.configure_coarsening(copy_of_partition_config, &edge_matcher, level); + rating.rate(*finer, level); + + edge_matcher->match(copy_of_partition_config, *finer, edge_matching, + *coarse_mapping, no_of_coarser_vertices, permutation); + delete edge_matcher; + + if(partition_config.graph_allready_partitioned) { + contracter->contract_partitioned(copy_of_partition_config, *finer, *coarser, edge_matching, + *coarse_mapping, no_of_coarser_vertices, permutation); + } else { + contracter->contract(copy_of_partition_config, *finer, *coarser, edge_matching, + *coarse_mapping, no_of_coarser_vertices, permutation); + } + + hierarchy.push_back(finer, coarse_mapping); + contraction_stop = coarsening_stop_rule->stop(no_of_finer_vertices, no_of_coarser_vertices); + + no_of_finer_vertices = no_of_coarser_vertices; + PRINT(std::cout << "no of coarser vertices " << no_of_coarser_vertices + << " and no of edges " << coarser->number_of_edges() << std::endl;) + + finer = coarser; + + level++; + } while( contraction_stop ); + + hierarchy.push_back(finer, NULL); // append the last created level + + delete contracter; + delete coarsening_stop_rule; +} +} diff --git a/parallel/modified_kahip/lib/partition/coarsening/coarsening.h b/parallel/modified_kahip/lib/partition/coarsening/coarsening.h index a6f98a53..2a133bb8 100644 --- a/parallel/modified_kahip/lib/partition/coarsening/coarsening.h +++ b/parallel/modified_kahip/lib/partition/coarsening/coarsening.h @@ -11,13 +11,13 @@ #include "data_structure/graph_access.h" #include "data_structure/graph_hierarchy.h" #include "partition_config.h" - +namespace kahip::modified { class coarsening { public: - coarsening (); - virtual ~coarsening (); + coarsening (); + virtual ~coarsening (); - void perform_coarsening(const PartitionConfig & config, graph_access & G, graph_hierarchy & hierarchy); + void perform_coarsening(const PartitionConfig & config, graph_access & G, graph_hierarchy & hierarchy); }; - +} #endif /* end of include guard: COARSENING_UU97ZBTR */ diff --git a/parallel/modified_kahip/lib/partition/coarsening/coarsening_configurator.h b/parallel/modified_kahip/lib/partition/coarsening/coarsening_configurator.h index e8d7b309..d803efd0 100644 --- a/parallel/modified_kahip/lib/partition/coarsening/coarsening_configurator.h +++ b/parallel/modified_kahip/lib/partition/coarsening/coarsening_configurator.h @@ -16,47 +16,48 @@ #include "matching/random_matching.h" #include "clustering/size_constraint_label_propagation.h" #include "stop_rules/stop_rules.h" - +namespace kahip::modified { class coarsening_configurator { - public: - coarsening_configurator( ) {}; - virtual ~coarsening_configurator() {}; +public: + coarsening_configurator( ) {}; + virtual ~coarsening_configurator() {}; - void configure_coarsening(const PartitionConfig & partition_config, - matching** edge_matcher, - unsigned level); + void configure_coarsening(const PartitionConfig & partition_config, + matching** edge_matcher, + unsigned level); }; -inline void coarsening_configurator::configure_coarsening( const PartitionConfig & partition_config, - matching** edge_matcher, +inline void coarsening_configurator::configure_coarsening( const PartitionConfig & partition_config, + matching** edge_matcher, unsigned level) { switch(partition_config.matching_type) { - case MATCHING_RANDOM: + case MATCHING_RANDOM: *edge_matcher = new random_matching(); - break; + break; case MATCHING_GPA: *edge_matcher = new gpa_matching(); - PRINT(std::cout << "gpa matching" << std::endl;) - break; + PRINT(std::cout << "gpa matching" << std::endl;) + break; case MATCHING_RANDOM_GPA: PRINT(std::cout << "random gpa matching" << std::endl;) *edge_matcher = new gpa_matching(); - break; - case CLUSTER_COARSENING: + break; + case CLUSTER_COARSENING: PRINT(std::cout << "cluster_coarsening" << std::endl;) *edge_matcher = new size_constraint_label_propagation(); - break; + break; } - if( partition_config.matching_type == MATCHING_RANDOM_GPA + if( partition_config.matching_type == MATCHING_RANDOM_GPA && level < partition_config.aggressive_random_levels) { delete *edge_matcher; PRINT(std::cout << "random matching" << std::endl;) *edge_matcher = new random_matching(); - } + } +} } #endif /* end of include guard: COARSENING_CONFIGURATOR_8UJ78WYS */ diff --git a/parallel/modified_kahip/lib/partition/coarsening/contraction.cpp b/parallel/modified_kahip/lib/partition/coarsening/contraction.cpp index 75a1b2bb..0b72999a 100644 --- a/parallel/modified_kahip/lib/partition/coarsening/contraction.cpp +++ b/parallel/modified_kahip/lib/partition/coarsening/contraction.cpp @@ -9,13 +9,7 @@ #include "../uncoarsening/refinement/quotient_graph_refinement/complete_boundary.h" #include "macros_assertions.h" -contraction::contraction() { - -} - -contraction::~contraction() { - -} +namespace kahip::modified { // for documentation see technical reports of christian schulz void contraction::contract(const PartitionConfig & partition_config, @@ -26,187 +20,187 @@ void contraction::contract(const PartitionConfig & partition_config, const NodeID & no_of_coarse_vertices, const NodePermutationMap & permutation) const { - if(partition_config.matching_type == CLUSTER_COARSENING) { - return contract_clustering(partition_config, G, coarser, edge_matching, coarse_mapping, no_of_coarse_vertices, permutation); - } - - if(partition_config.combine) { - coarser.resizeSecondPartitionIndex(no_of_coarse_vertices); - } - - std::vector new_edge_targets(G.number_of_edges()); - forall_edges(G, e) { - new_edge_targets[e] = coarse_mapping[G.getEdgeTarget(e)]; - } endfor - - std::vector edge_positions(no_of_coarse_vertices, UNDEFINED_EDGE); - - //we dont know the number of edges jet, so we use the old number for - //construction of the coarser graph and then resize the field according - //to the number of edges we really got - coarser.start_construction(no_of_coarse_vertices, G.number_of_edges()); - - NodeID cur_no_vertices = 0; - - forall_nodes(G, n) { - NodeID node = permutation[n]; - //we look only at the coarser nodes - if(coarse_mapping[node] != cur_no_vertices) - continue; - - NodeID coarseNode = coarser.new_node(); - coarser.setNodeWeight(coarseNode, G.getNodeWeight(node)); - - if(partition_config.combine) { - coarser.setSecondPartitionIndex(coarseNode, G.getSecondPartitionIndex(node)); - } - - // do something with all outgoing edges (in auxillary graph) - forall_out_edges(G, e, node) { - visit_edge(G, coarser, edge_positions, coarseNode, e, new_edge_targets); - } endfor - - //this node was really matched - NodeID matched_neighbor = edge_matching[node]; - if(node != matched_neighbor) { - //update weight of coarser node - NodeWeight new_coarse_weight = G.getNodeWeight(node) + G.getNodeWeight(matched_neighbor); - coarser.setNodeWeight(coarseNode, new_coarse_weight); - - forall_out_edges(G, e, matched_neighbor) { - visit_edge(G, coarser, edge_positions, coarseNode, e, new_edge_targets); - } endfor - } - forall_out_edges(coarser, e, coarseNode) { - edge_positions[coarser.getEdgeTarget(e)] = UNDEFINED_EDGE; - } endfor - - cur_no_vertices++; - } endfor - - ASSERT_RANGE_EQ(edge_positions, 0, edge_positions.size(), UNDEFINED_EDGE); - ASSERT_EQ(no_of_coarse_vertices, cur_no_vertices); - - //this also resizes the edge fields ... - coarser.finish_construction(); + if(partition_config.matching_type == CLUSTER_COARSENING) { + return contract_clustering(partition_config, G, coarser, edge_matching, coarse_mapping, no_of_coarse_vertices, permutation); + } + + if(partition_config.combine) { + coarser.resizeSecondPartitionIndex(no_of_coarse_vertices); + } + + std::vector new_edge_targets(G.number_of_edges()); + forall_edges(G, e) { + new_edge_targets[e] = coarse_mapping[G.getEdgeTarget(e)]; + } endfor + + std::vector edge_positions(no_of_coarse_vertices, UNDEFINED_EDGE); + + //we dont know the number of edges jet, so we use the old number for + //construction of the coarser graph and then resize the field according + //to the number of edges we really got + coarser.start_construction(no_of_coarse_vertices, G.number_of_edges()); + + NodeID cur_no_vertices = 0; + + forall_nodes(G, n) { + NodeID node = permutation[n]; + //we look only at the coarser nodes + if(coarse_mapping[node] != cur_no_vertices) + continue; + + NodeID coarseNode = coarser.new_node(); + coarser.setNodeWeight(coarseNode, G.getNodeWeight(node)); + + if(partition_config.combine) { + coarser.setSecondPartitionIndex(coarseNode, G.getSecondPartitionIndex(node)); + } + + // do something with all outgoing edges (in auxillary graph) + forall_out_edges(G, e, node) { + visit_edge(G, coarser, edge_positions, coarseNode, e, new_edge_targets); + } endfor + + //this node was really matched + NodeID matched_neighbor = edge_matching[node]; + if(node != matched_neighbor) { + //update weight of coarser node + NodeWeight new_coarse_weight = G.getNodeWeight(node) + G.getNodeWeight(matched_neighbor); + coarser.setNodeWeight(coarseNode, new_coarse_weight); + + forall_out_edges(G, e, matched_neighbor) { + visit_edge(G, coarser, edge_positions, coarseNode, e, new_edge_targets); + } endfor +} + forall_out_edges(coarser, e, coarseNode) { + edge_positions[coarser.getEdgeTarget(e)] = UNDEFINED_EDGE; + } endfor + + cur_no_vertices++; + } endfor + + ASSERT_RANGE_EQ(edge_positions, 0, edge_positions.size(), UNDEFINED_EDGE); + ASSERT_EQ(no_of_coarse_vertices, cur_no_vertices); + + //this also resizes the edge fields ... + coarser.finish_construction(); } -void contraction::contract_clustering(const PartitionConfig & partition_config, - graph_access & G, - graph_access & coarser, +void contraction::contract_clustering(const PartitionConfig & partition_config, + graph_access & G, + graph_access & coarser, const Matching & edge_matching, const CoarseMapping & coarse_mapping, const NodeID & no_of_coarse_vertices, const NodePermutationMap & permutation) const { - if(partition_config.combine) { - coarser.resizeSecondPartitionIndex(no_of_coarse_vertices); - } + if(partition_config.combine) { + coarser.resizeSecondPartitionIndex(no_of_coarse_vertices); + } - //save partition map -- important if the graph is allready partitioned - std::vector< int > partition_map(G.number_of_nodes()); - int k = G.get_partition_count(); - forall_nodes(G, node) { - partition_map[node] = G.getPartitionIndex(node); - G.setPartitionIndex(node, coarse_mapping[node]); - } endfor + //save partition map -- important if the graph is allready partitioned + std::vector< int > partition_map(G.number_of_nodes()); + int k = G.get_partition_count(); + forall_nodes(G, node) { + partition_map[node] = G.getPartitionIndex(node); + G.setPartitionIndex(node, coarse_mapping[node]); + } endfor - G.set_partition_count(no_of_coarse_vertices); + G.set_partition_count(no_of_coarse_vertices); - complete_boundary bnd(&G); - bnd.build(); - bnd.getUnderlyingQuotientGraph(coarser); + complete_boundary bnd(&G); + bnd.build(); + bnd.getUnderlyingQuotientGraph(coarser); - G.set_partition_count(k); - forall_nodes(G, node) { - G.setPartitionIndex(node, partition_map[node]); - coarser.setPartitionIndex(coarse_mapping[node], G.getPartitionIndex(node)); + G.set_partition_count(k); + forall_nodes(G, node) { + G.setPartitionIndex(node, partition_map[node]); + coarser.setPartitionIndex(coarse_mapping[node], G.getPartitionIndex(node)); - if(partition_config.combine) { - coarser.setSecondPartitionIndex(coarse_mapping[node], G.getSecondPartitionIndex(node)); - } + if(partition_config.combine) { + coarser.setSecondPartitionIndex(coarse_mapping[node], G.getSecondPartitionIndex(node)); + } - } endfor + } endfor } -// for documentation see technical reports of christian schulz -void contraction::contract_partitioned(const PartitionConfig & partition_config, - graph_access & G, - graph_access & coarser, +// for documentation see technical reports of christian schulz +void contraction::contract_partitioned(const PartitionConfig & partition_config, + graph_access & G, + graph_access & coarser, const Matching & edge_matching, const CoarseMapping & coarse_mapping, const NodeID & no_of_coarse_vertices, const NodePermutationMap & permutation) const { - - if(partition_config.matching_type == CLUSTER_COARSENING) { - return contract_clustering(partition_config, G, coarser, edge_matching, coarse_mapping, no_of_coarse_vertices, permutation); - } - - - std::vector new_edge_targets(G.number_of_edges()); - forall_edges(G, e) { - new_edge_targets[e] = coarse_mapping[G.getEdgeTarget(e)]; - } endfor - - std::vector edge_positions(no_of_coarse_vertices, UNDEFINED_EDGE); - - //we dont know the number of edges jet, so we use the old number for - //construction of the coarser graph and then resize the field according - //to the number of edges we really got - coarser.set_partition_count(G.get_partition_count()); - coarser.start_construction(no_of_coarse_vertices, G.number_of_edges()); - - if(partition_config.combine) { - coarser.resizeSecondPartitionIndex(no_of_coarse_vertices); - } - - NodeID cur_no_vertices = 0; - - PRINT(std::cout << "contracting a partitioned graph" << std::endl;) - forall_nodes(G, n) { - NodeID node = permutation[n]; - //we look only at the coarser nodes - if(coarse_mapping[node] != cur_no_vertices) - continue; - - NodeID coarseNode = coarser.new_node(); - coarser.setNodeWeight(coarseNode, G.getNodeWeight(node)); - coarser.setPartitionIndex(coarseNode, G.getPartitionIndex(node)); - - if(partition_config.combine) { - coarser.setSecondPartitionIndex(coarseNode, G.getSecondPartitionIndex(node)); - } - // do something with all outgoing edges (in auxillary graph) - forall_out_edges(G, e, node) { - visit_edge(G, coarser, edge_positions, coarseNode, e, new_edge_targets); - } endfor - - //this node was really matched - NodeID matched_neighbor = edge_matching[node]; - if(node != matched_neighbor) { - //update weight of coarser node - NodeWeight new_coarse_weight = G.getNodeWeight(node) + G.getNodeWeight(matched_neighbor); - coarser.setNodeWeight(coarseNode, new_coarse_weight); - - forall_out_edges(G, e, matched_neighbor) { - visit_edge(G, coarser, edge_positions, coarseNode, e, new_edge_targets); - } endfor - } - forall_out_edges(coarser, e, coarseNode) { - edge_positions[coarser.getEdgeTarget(e)] = UNDEFINED_EDGE; - } endfor - - cur_no_vertices++; - } endfor - - ASSERT_RANGE_EQ(edge_positions, 0, edge_positions.size(), UNDEFINED_EDGE); - ASSERT_EQ(no_of_coarse_vertices, cur_no_vertices); - - //this also resizes the edge fields ... - coarser.finish_construction(); + + if(partition_config.matching_type == CLUSTER_COARSENING) { + return contract_clustering(partition_config, G, coarser, edge_matching, coarse_mapping, no_of_coarse_vertices, permutation); + } + + + std::vector new_edge_targets(G.number_of_edges()); + forall_edges(G, e) { + new_edge_targets[e] = coarse_mapping[G.getEdgeTarget(e)]; + } endfor + + std::vector edge_positions(no_of_coarse_vertices, UNDEFINED_EDGE); + + //we dont know the number of edges jet, so we use the old number for + //construction of the coarser graph and then resize the field according + //to the number of edges we really got + coarser.set_partition_count(G.get_partition_count()); + coarser.start_construction(no_of_coarse_vertices, G.number_of_edges()); + + if(partition_config.combine) { + coarser.resizeSecondPartitionIndex(no_of_coarse_vertices); + } + + NodeID cur_no_vertices = 0; + + PRINT(std::cout << "contracting a partitioned graph" << std::endl;) + forall_nodes(G, n) { + NodeID node = permutation[n]; + //we look only at the coarser nodes + if(coarse_mapping[node] != cur_no_vertices) + continue; + + NodeID coarseNode = coarser.new_node(); + coarser.setNodeWeight(coarseNode, G.getNodeWeight(node)); + coarser.setPartitionIndex(coarseNode, G.getPartitionIndex(node)); + + if(partition_config.combine) { + coarser.setSecondPartitionIndex(coarseNode, G.getSecondPartitionIndex(node)); + } + // do something with all outgoing edges (in auxillary graph) + forall_out_edges(G, e, node) { + visit_edge(G, coarser, edge_positions, coarseNode, e, new_edge_targets); + } endfor + + //this node was really matched + NodeID matched_neighbor = edge_matching[node]; + if(node != matched_neighbor) { + //update weight of coarser node + NodeWeight new_coarse_weight = G.getNodeWeight(node) + G.getNodeWeight(matched_neighbor); + coarser.setNodeWeight(coarseNode, new_coarse_weight); + + forall_out_edges(G, e, matched_neighbor) { + visit_edge(G, coarser, edge_positions, coarseNode, e, new_edge_targets); + } endfor } + forall_out_edges(coarser, e, coarseNode) { + edge_positions[coarser.getEdgeTarget(e)] = UNDEFINED_EDGE; + } endfor + + cur_no_vertices++; + } endfor + ASSERT_RANGE_EQ(edge_positions, 0, edge_positions.size(), UNDEFINED_EDGE); + ASSERT_EQ(no_of_coarse_vertices, cur_no_vertices); + + //this also resizes the edge fields ... + coarser.finish_construction(); +} +} diff --git a/parallel/modified_kahip/lib/partition/coarsening/contraction.h b/parallel/modified_kahip/lib/partition/coarsening/contraction.h index 39b8732f..80a3a7d4 100644 --- a/parallel/modified_kahip/lib/partition/coarsening/contraction.h +++ b/parallel/modified_kahip/lib/partition/coarsening/contraction.h @@ -12,47 +12,47 @@ #include "data_structure/graph_access.h" #include "matching/matching.h" #include "partition_config.h" - +namespace kahip::modified { typedef NodeID Regions; class contraction { - public: - contraction(); - virtual ~contraction(); - - void contract(const PartitionConfig & partition_config, - graph_access & finer, - graph_access & coarser, - const Matching & edge_matching, - const CoarseMapping & coarse_mapping, - const NodeID & no_of_coarse_vertices, - const NodePermutationMap & permutation) const; - - void contract_clustering(const PartitionConfig & partition_config, - graph_access & finer, - graph_access & coarser, - const Matching & edge_matching, - const CoarseMapping & coarse_mapping, - const NodeID & no_of_coarse_vertices, - const NodePermutationMap & permutation) const; - - - void contract_partitioned(const PartitionConfig & partition_config, - graph_access & G, - graph_access & coarser, - const Matching & edge_matching, - const CoarseMapping & coarse_mapping, - const NodeID & no_of_coarse_vertices, - const NodePermutationMap & permutation) const; - - private: - // visits an edge in G (and auxillary graph) and updates/creates and edge in coarser graph - void visit_edge(graph_access & G, - graph_access & coarser, - std::vector & edge_positions, - const NodeID coarseNode, - const EdgeID e, - const std::vector & new_edge_targets) const; +public: + contraction() = default; + virtual ~contraction() = default; + + void contract(const PartitionConfig & partition_config, + graph_access & finer, + graph_access & coarser, + const Matching & edge_matching, + const CoarseMapping & coarse_mapping, + const NodeID & no_of_coarse_vertices, + const NodePermutationMap & permutation) const; + + void contract_clustering(const PartitionConfig & partition_config, + graph_access & finer, + graph_access & coarser, + const Matching & edge_matching, + const CoarseMapping & coarse_mapping, + const NodeID & no_of_coarse_vertices, + const NodePermutationMap & permutation) const; + + + void contract_partitioned(const PartitionConfig & partition_config, + graph_access & G, + graph_access & coarser, + const Matching & edge_matching, + const CoarseMapping & coarse_mapping, + const NodeID & no_of_coarse_vertices, + const NodePermutationMap & permutation) const; + +private: + // visits an edge in G (and auxillary graph) and updates/creates and edge in coarser graph + void visit_edge(graph_access & G, + graph_access & coarser, + std::vector & edge_positions, + const NodeID coarseNode, + const EdgeID e, + const std::vector & new_edge_targets) const; }; @@ -80,7 +80,7 @@ inline void contraction::visit_edge(graph_access & G, coarser.setEdgeWeight(edge_pos, new_edge_weight); } } - +} #endif /* end of include guard: CONTRACTION_VIXZ9K0F */ diff --git a/parallel/modified_kahip/lib/partition/coarsening/edge_rating/edge_ratings.cpp b/parallel/modified_kahip/lib/partition/coarsening/edge_rating/edge_ratings.cpp index c03b7fa5..1f3b4cb3 100644 --- a/parallel/modified_kahip/lib/partition/coarsening/edge_rating/edge_ratings.cpp +++ b/parallel/modified_kahip/lib/partition/coarsening/edge_rating/edge_ratings.cpp @@ -10,7 +10,7 @@ #include "edge_ratings.h" #include "partition_config.h" #include "random_functions.h" - +namespace kahip::modified { edge_ratings::edge_ratings(const PartitionConfig & _partition_config) : partition_config(_partition_config){ } @@ -20,168 +20,169 @@ edge_ratings::~edge_ratings() { } void edge_ratings::rate(graph_access & G, unsigned level) { - //rate the edges - if(level == 0 && partition_config.first_level_random_matching) { - return; - } else if(partition_config.matching_type == MATCHING_RANDOM_GPA && level < partition_config.aggressive_random_levels) { - return; - } - if(level == 0 && partition_config.rate_first_level_inner_outer && - partition_config.edge_rating != EXPANSIONSTAR2ALGDIST ) { - - rate_inner_outer(G); - - } else if(partition_config.matching_type != MATCHING_RANDOM) { - switch(partition_config.edge_rating) { - case EXPANSIONSTAR: - rate_expansion_star(G); - break; - case PSEUDOGEOM: - rate_pseudogeom(G); - break; - case EXPANSIONSTAR2: - rate_expansion_star_2(G); - break; - case EXPANSIONSTAR2ALGDIST: - rate_expansion_star_2_algdist(G); - break; - case WEIGHT: - break; - } - } + //rate the edges + if(level == 0 && partition_config.first_level_random_matching) { + return; + } else if(partition_config.matching_type == MATCHING_RANDOM_GPA && level < partition_config.aggressive_random_levels) { + return; + } + if(level == 0 && partition_config.rate_first_level_inner_outer && + partition_config.edge_rating != EXPANSIONSTAR2ALGDIST ) { + + rate_inner_outer(G); + + } else if(partition_config.matching_type != MATCHING_RANDOM) { + switch(partition_config.edge_rating) { + case EXPANSIONSTAR: + rate_expansion_star(G); + break; + case PSEUDOGEOM: + rate_pseudogeom(G); + break; + case EXPANSIONSTAR2: + rate_expansion_star_2(G); + break; + case EXPANSIONSTAR2ALGDIST: + rate_expansion_star_2_algdist(G); + break; + case WEIGHT: + break; + } + } } //simd implementation is possible void edge_ratings::compute_algdist(graph_access & G, std::vector & dist) { - for( unsigned R = 0; R < 3; R++) { - std::vector prev(G.number_of_nodes(), 0); - forall_nodes(G, node) { - prev[node] = random_functions::nextDouble(-0.5,0.5); - } endfor - - std::vector next(G.number_of_nodes(), 0); - float w = 0.5; - - for( unsigned k = 0; k < 7; k++) { - forall_nodes(G, node) { - next[node] = 0; - - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - next[node] += prev[target] * G.getEdgeWeight(e); - } endfor - - float wdegree = G.getWeightedNodeDegree(node); - if(wdegree > 0) { - next[node] /= (float)wdegree; - - } - } endfor - - forall_nodes(G, node) { - prev[node] = (1-w)*prev[node] + w*next[node]; - } endfor - - } - - forall_nodes(G, node) { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - //dist[e] = max(dist[e],fabs(prev[node] - prev[target])); - dist[e] += fabs(prev[node] - prev[target]) / 7.0; - } endfor - } endfor + for( unsigned R = 0; R < 3; R++) { + std::vector prev(G.number_of_nodes(), 0); + forall_nodes(G, node) { + prev[node] = random_functions::nextDouble(-0.5,0.5); + } endfor + + std::vector next(G.number_of_nodes(), 0); + float w = 0.5; + + for( unsigned k = 0; k < 7; k++) { + forall_nodes(G, node) { + next[node] = 0; + + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + next[node] += prev[target] * G.getEdgeWeight(e); + } endfor + + float wdegree = G.getWeightedNodeDegree(node); + if(wdegree > 0) { + next[node] /= (float)wdegree; + } + } endfor - forall_nodes(G, node) { - forall_out_edges(G, e, node) { - dist[e] += 0.0001; - } endfor - } endfor + forall_nodes(G, node) { + prev[node] = (1-w)*prev[node] + w*next[node]; + } endfor + +} + + forall_nodes(G, node) { + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + //dist[e] = max(dist[e],fabs(prev[node] - prev[target])); + dist[e] += fabs(prev[node] - prev[target]) / 7.0; + } endfor +} endfor +} + + forall_nodes(G, node) { + forall_out_edges(G, e, node) { + dist[e] += 0.0001; + } endfor +} endfor } void edge_ratings::rate_expansion_star_2_algdist(graph_access & G) { - std::vector dist(G.number_of_edges(), 0); - compute_algdist(G, dist); + std::vector dist(G.number_of_edges(), 0); + compute_algdist(G, dist); - forall_nodes(G,n) { - NodeWeight sourceWeight = G.getNodeWeight(n); - forall_out_edges(G, e, n) { - NodeID targetNode = G.getEdgeTarget(e); - NodeWeight targetWeight = G.getNodeWeight(targetNode); - EdgeWeight edgeWeight = G.getEdgeWeight(e); + forall_nodes(G,n) { + NodeWeight sourceWeight = G.getNodeWeight(n); + forall_out_edges(G, e, n) { + NodeID targetNode = G.getEdgeTarget(e); + NodeWeight targetWeight = G.getNodeWeight(targetNode); + EdgeWeight edgeWeight = G.getEdgeWeight(e); - EdgeRatingType rating = 1.0*edgeWeight*edgeWeight / (targetWeight*sourceWeight*dist[e]); - G.setEdgeRating(e, rating); - } endfor - } endfor + EdgeRatingType rating = 1.0*edgeWeight*edgeWeight / (targetWeight*sourceWeight*dist[e]); + G.setEdgeRating(e, rating); + } endfor +} endfor } void edge_ratings::rate_expansion_star_2(graph_access & G) { - forall_nodes(G,n) { - NodeWeight sourceWeight = G.getNodeWeight(n); - forall_out_edges(G, e, n) { - NodeID targetNode = G.getEdgeTarget(e); - NodeWeight targetWeight = G.getNodeWeight(targetNode); - EdgeWeight edgeWeight = G.getEdgeWeight(e); - - EdgeRatingType rating = 1.0*edgeWeight*edgeWeight / (targetWeight*sourceWeight); - G.setEdgeRating(e, rating); - } endfor - } endfor + forall_nodes(G,n) { + NodeWeight sourceWeight = G.getNodeWeight(n); + forall_out_edges(G, e, n) { + NodeID targetNode = G.getEdgeTarget(e); + NodeWeight targetWeight = G.getNodeWeight(targetNode); + EdgeWeight edgeWeight = G.getEdgeWeight(e); + + EdgeRatingType rating = 1.0*edgeWeight*edgeWeight / (targetWeight*sourceWeight); + G.setEdgeRating(e, rating); + } endfor +} endfor } void edge_ratings::rate_inner_outer(graph_access & G) { - forall_nodes(G,n) { + forall_nodes(G,n) { #ifndef WALSHAWMH - EdgeWeight sourceDegree = G.getWeightedNodeDegree(n); + EdgeWeight sourceDegree = G.getWeightedNodeDegree(n); #else - EdgeWeight sourceDegree = G.getNodeDegree(n); + EdgeWeight sourceDegree = G.getNodeDegree(n); #endif - if(sourceDegree == 0) continue; + if(sourceDegree == 0) continue; - forall_out_edges(G, e, n) { - NodeID targetNode = G.getEdgeTarget(e); + forall_out_edges(G, e, n) { + NodeID targetNode = G.getEdgeTarget(e); #ifndef WALSHAWMH - EdgeWeight targetDegree = G.getWeightedNodeDegree(targetNode); + EdgeWeight targetDegree = G.getWeightedNodeDegree(targetNode); #else - EdgeWeight targetDegree = G.getNodeDegree(targetNode); + EdgeWeight targetDegree = G.getNodeDegree(targetNode); #endif - EdgeWeight edgeWeight = G.getEdgeWeight(e); - EdgeRatingType rating = 1.0*edgeWeight/(sourceDegree+targetDegree - edgeWeight); - G.setEdgeRating(e, rating); - } endfor - } endfor + EdgeWeight edgeWeight = G.getEdgeWeight(e); + EdgeRatingType rating = 1.0*edgeWeight/(sourceDegree+targetDegree - edgeWeight); + G.setEdgeRating(e, rating); + } endfor +} endfor } void edge_ratings::rate_expansion_star(graph_access & G) { - forall_nodes(G,n) { - NodeWeight sourceWeight = G.getNodeWeight(n); - forall_out_edges(G, e, n) { - NodeID targetNode = G.getEdgeTarget(e); - NodeWeight targetWeight = G.getNodeWeight(targetNode); - EdgeWeight edgeWeight = G.getEdgeWeight(e); - - EdgeRatingType rating = 1.0 * edgeWeight / (targetWeight*sourceWeight); - G.setEdgeRating(e, rating); - } endfor - } endfor + forall_nodes(G,n) { + NodeWeight sourceWeight = G.getNodeWeight(n); + forall_out_edges(G, e, n) { + NodeID targetNode = G.getEdgeTarget(e); + NodeWeight targetWeight = G.getNodeWeight(targetNode); + EdgeWeight edgeWeight = G.getEdgeWeight(e); + + EdgeRatingType rating = 1.0 * edgeWeight / (targetWeight*sourceWeight); + G.setEdgeRating(e, rating); + } endfor +} endfor } void edge_ratings::rate_pseudogeom(graph_access & G) { - forall_nodes(G,n) { - NodeWeight sourceWeight = G.getNodeWeight(n); - forall_out_edges(G, e, n) { - NodeID targetNode = G.getEdgeTarget(e); - NodeWeight targetWeight = G.getNodeWeight(targetNode); - EdgeWeight edgeWeight = G.getEdgeWeight(e); - double random_term = random_functions::nextDouble(0.6,1.0); - EdgeRatingType rating = random_term * edgeWeight * (1.0/(double)sqrt((double)targetWeight) + 1.0/(double)sqrt((double)sourceWeight)); - G.setEdgeRating(e, rating); - } endfor - } endfor + forall_nodes(G,n) { + NodeWeight sourceWeight = G.getNodeWeight(n); + forall_out_edges(G, e, n) { + NodeID targetNode = G.getEdgeTarget(e); + NodeWeight targetWeight = G.getNodeWeight(targetNode); + EdgeWeight edgeWeight = G.getEdgeWeight(e); + double random_term = random_functions::nextDouble(0.6,1.0); + EdgeRatingType rating = random_term * edgeWeight * (1.0/(double)sqrt((double)targetWeight) + 1.0/(double)sqrt((double)sourceWeight)); + G.setEdgeRating(e, rating); + } endfor +} endfor } +} \ No newline at end of file diff --git a/parallel/modified_kahip/lib/partition/coarsening/edge_rating/edge_ratings.h b/parallel/modified_kahip/lib/partition/coarsening/edge_rating/edge_ratings.h index 1e6bff74..78b9b857 100644 --- a/parallel/modified_kahip/lib/partition/coarsening/edge_rating/edge_ratings.h +++ b/parallel/modified_kahip/lib/partition/coarsening/edge_rating/edge_ratings.h @@ -10,22 +10,22 @@ #include "data_structure/graph_access.h" #include "partition_config.h" - +namespace kahip::modified { class edge_ratings { public: - edge_ratings(const PartitionConfig & partition_config); - virtual ~edge_ratings(); + edge_ratings(const PartitionConfig & partition_config); + virtual ~edge_ratings(); - void rate(graph_access & G, unsigned level); - void rate_expansion_star_2(graph_access & G); - void rate_expansion_star(graph_access & G); - void rate_expansion_star_2_algdist(graph_access & G); - void rate_inner_outer(graph_access & G); - void rate_pseudogeom(graph_access & G); - void compute_algdist(graph_access & G, std::vector & dist); + void rate(graph_access & G, unsigned level); + void rate_expansion_star_2(graph_access & G); + void rate_expansion_star(graph_access & G); + void rate_expansion_star_2_algdist(graph_access & G); + void rate_inner_outer(graph_access & G); + void rate_pseudogeom(graph_access & G); + void compute_algdist(graph_access & G, std::vector & dist); private: - const PartitionConfig & partition_config; + const PartitionConfig & partition_config; }; - +} #endif /* end of include guard: EDGE_RATING_FUNCTIONS_FUCW7H6Y */ diff --git a/parallel/modified_kahip/lib/partition/coarsening/matching/compare_degrees.h b/parallel/modified_kahip/lib/partition/coarsening/matching/compare_degrees.h index 69b59ca8..f66b7fba 100644 --- a/parallel/modified_kahip/lib/partition/coarsening/matching/compare_degrees.h +++ b/parallel/modified_kahip/lib/partition/coarsening/matching/compare_degrees.h @@ -7,6 +7,10 @@ #ifndef COMPARE_DEGREES_750FUZ7Z #define COMPARE_DEGREES_750FUZ7Z +#include + +#include "definitions.h" +namespace kahip::modified { class compare_degrees : public std::binary_function { public: @@ -20,6 +24,6 @@ class compare_degrees : public std::binary_function * m_node_degrees; }; - +} #endif /* end of include guard: COMPARE_DEGREES_750FUZ7Z */ diff --git a/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/compare_rating.h b/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/compare_rating.h index 835b193f..800f60ce 100644 --- a/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/compare_rating.h +++ b/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/compare_rating.h @@ -10,19 +10,19 @@ #include "data_structure/graph_access.h" #include "definitions.h" - +namespace kahip::modified { class compare_rating { - public: - compare_rating(graph_access * pG) : G(pG) {}; - virtual ~compare_rating() {}; +public: + compare_rating(graph_access * pG) : G(pG) {}; + virtual ~compare_rating() {}; - bool operator() (const EdgeRatingType left, const EdgeRatingType right ) { - return G->getEdgeRating(left) > G->getEdgeRating(right); - } + bool operator() (const EdgeRatingType left, const EdgeRatingType right ) { + return G->getEdgeRating(left) > G->getEdgeRating(right); + } - private: - graph_access * G; +private: + graph_access * G; }; - +} #endif /* end of include guard: COMPARE_RATING_750FUZ7Z */ diff --git a/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/gpa_matching.cpp b/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/gpa_matching.cpp index 2c95552b..dc203099 100644 --- a/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/gpa_matching.cpp +++ b/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/gpa_matching.cpp @@ -12,7 +12,7 @@ #include "gpa_matching.h" #include "macros_assertions.h" #include "random_functions.h" - +namespace kahip::modified { gpa_matching::gpa_matching() { } @@ -28,332 +28,333 @@ void gpa_matching::match(const PartitionConfig & partition_config, CoarseMapping & coarse_mapping, NodeID & no_of_coarse_vertices, NodePermutationMap & permutation) { - PRINT(std::cout<< "matching using gpa" << std::endl;) - permutation.resize(G.number_of_nodes()); - edge_matching.resize(G.number_of_nodes()); - coarse_mapping.resize(G.number_of_nodes()); - - std::vector edge_permutation; - edge_permutation.reserve(G.number_of_edges()); - std::vector sources(G.number_of_edges()); - - init(G, partition_config, permutation, edge_matching, edge_permutation, sources); - - //permutation of the edges for random tie breaking - if(partition_config.edge_rating_tiebreaking) { - PartitionConfig gpa_perm_config = partition_config; - gpa_perm_config.permutation_quality = PERMUTATION_QUALITY_GOOD; - random_functions::permutate_entries(gpa_perm_config, edge_permutation, false); + PRINT(std::cout<< "matching using gpa" << std::endl;) + permutation.resize(G.number_of_nodes()); + edge_matching.resize(G.number_of_nodes()); + coarse_mapping.resize(G.number_of_nodes()); + + std::vector edge_permutation; + edge_permutation.reserve(G.number_of_edges()); + std::vector sources(G.number_of_edges()); + + init(G, partition_config, permutation, edge_matching, edge_permutation, sources); + + //permutation of the edges for random tie breaking + if(partition_config.edge_rating_tiebreaking) { + PartitionConfig gpa_perm_config = partition_config; + gpa_perm_config.permutation_quality = PERMUTATION_QUALITY_GOOD; + random_functions::permutate_entries(gpa_perm_config, edge_permutation, false); + } + + compare_rating cmp(&G); + std::sort(edge_permutation.begin(), edge_permutation.end(), cmp); + + path_set pathset(&G, &partition_config); + + //grow the paths + forall_edges(G, e) { + EdgeID curEdge = edge_permutation[e]; + NodeID source = sources[curEdge]; + NodeID target = G.getEdgeTarget(curEdge); + if(target < source) continue; // get rid of double edges + + if(G.getEdgeRating(curEdge) == 0.0) { + continue; + } + + //max vertex weight constraint + if(G.getNodeWeight(source) + G.getNodeWeight(target) > partition_config.max_vertex_weight) { + continue; + } + + if( partition_config.combine ) { + if(G.getSecondPartitionIndex(source) != G.getSecondPartitionIndex(target) ) { + std::cout << "b" << std::endl; + continue; + } + } + + pathset.add_if_applicable(source, curEdge); + } endfor + + extract_paths_apply_matching(G, sources, edge_matching, pathset); + + // all matched pairs are now in edge_matching + // now construct the coarsemapping + no_of_coarse_vertices = 0; + if(!partition_config.graph_allready_partitioned) { + forall_nodes(G, n) { + if(partition_config.combine) { + if(G.getSecondPartitionIndex(n) != G.getSecondPartitionIndex(edge_matching[n])) { + // v cycle... they shouldnt be contraced + edge_matching[n] = n; } - - compare_rating cmp(&G); - std::sort(edge_permutation.begin(), edge_permutation.end(), cmp); - - path_set pathset(&G, &partition_config); - - //grow the paths - forall_edges(G, e) { - EdgeID curEdge = edge_permutation[e]; - NodeID source = sources[curEdge]; - NodeID target = G.getEdgeTarget(curEdge); - if(target < source) continue; // get rid of double edges - - if(G.getEdgeRating(curEdge) == 0.0) { - continue; - } - - //max vertex weight constraint - if(G.getNodeWeight(source) + G.getNodeWeight(target) > partition_config.max_vertex_weight) { - continue; - } - - if( partition_config.combine ) { - if(G.getSecondPartitionIndex(source) != G.getSecondPartitionIndex(target) ) { - std::cout << "b" << std::endl; - continue; - } - } - - pathset.add_if_applicable(source, curEdge); - } endfor - - extract_paths_apply_matching(G, sources, edge_matching, pathset); - - // all matched pairs are now in edge_matching - // now construct the coarsemapping - no_of_coarse_vertices = 0; - if(!partition_config.graph_allready_partitioned) { - forall_nodes(G, n) { - if(partition_config.combine) { - if(G.getSecondPartitionIndex(n) != G.getSecondPartitionIndex(edge_matching[n])) { - // v cycle... they shouldnt be contraced - edge_matching[n] = n; - } - } - - if( n < edge_matching[n]) { - coarse_mapping[n] = no_of_coarse_vertices; - coarse_mapping[edge_matching[n]] = no_of_coarse_vertices; - no_of_coarse_vertices++; - } else if(n == edge_matching[n]) { - coarse_mapping[n] = no_of_coarse_vertices; - no_of_coarse_vertices++; - } - - } endfor - } else { - forall_nodes(G, n) { - if(G.getPartitionIndex(n) != G.getPartitionIndex(edge_matching[n])) { - // v cycle... they shouldnt be contraced - edge_matching[n] = n; - } - - if(partition_config.combine) { - if(G.getSecondPartitionIndex(n) != G.getSecondPartitionIndex(edge_matching[n])) { - // v cycle... they shouldnt be contraced - edge_matching[n] = n; - } - } - - - if( n < edge_matching[n]) { - coarse_mapping[n] = no_of_coarse_vertices; - coarse_mapping[edge_matching[n]] = no_of_coarse_vertices; - no_of_coarse_vertices++; - } else if(n == edge_matching[n]) { - coarse_mapping[n] = no_of_coarse_vertices; - no_of_coarse_vertices++; - } - - } endfor - } + } + + if( n < edge_matching[n]) { + coarse_mapping[n] = no_of_coarse_vertices; + coarse_mapping[edge_matching[n]] = no_of_coarse_vertices; + no_of_coarse_vertices++; + } else if(n == edge_matching[n]) { + coarse_mapping[n] = no_of_coarse_vertices; + no_of_coarse_vertices++; + } + + } endfor +} else { + forall_nodes(G, n) { + if(G.getPartitionIndex(n) != G.getPartitionIndex(edge_matching[n])) { + // v cycle... they shouldnt be contraced + edge_matching[n] = n; + } + + if(partition_config.combine) { + if(G.getSecondPartitionIndex(n) != G.getSecondPartitionIndex(edge_matching[n])) { + // v cycle... they shouldnt be contraced + edge_matching[n] = n; + } + } + + + if( n < edge_matching[n]) { + coarse_mapping[n] = no_of_coarse_vertices; + coarse_mapping[edge_matching[n]] = no_of_coarse_vertices; + no_of_coarse_vertices++; + } else if(n == edge_matching[n]) { + coarse_mapping[n] = no_of_coarse_vertices; + no_of_coarse_vertices++; + } + + } endfor +} } -void gpa_matching::init(graph_access & G, - const PartitionConfig & partition_config, - NodePermutationMap & permutation, - Matching & edge_matching, - std::vector & edge_permutation, +void gpa_matching::init(graph_access & G, + const PartitionConfig & partition_config, + NodePermutationMap & permutation, + Matching & edge_matching, + std::vector & edge_permutation, std::vector & sources) { - forall_nodes(G, n) { - permutation[n] = n; - edge_matching[n] = n; + forall_nodes(G, n) { + permutation[n] = n; + edge_matching[n] = n; - forall_out_edges(G, e, n) { - sources[e] = n; - edge_permutation.push_back(e); + forall_out_edges(G, e, n) { + sources[e] = n; + edge_permutation.push_back(e); - if(partition_config.edge_rating == WEIGHT) { - // in that case we need to copy it - G.setEdgeRating(e, G.getEdgeWeight(e)); - } + if(partition_config.edge_rating == WEIGHT) { + // in that case we need to copy it + G.setEdgeRating(e, G.getEdgeWeight(e)); + } - } endfor - } endfor + } endfor +} endfor } -void gpa_matching::extract_paths_apply_matching(graph_access & G, +void gpa_matching::extract_paths_apply_matching(graph_access & G, std::vector & sources, - Matching & edge_matching, + Matching & edge_matching, path_set & pathset) { - // extract the paths in the path set into lists of edges. - // then, apply the dynamic programming max weight function to them. Apply - // the matched edges. - EdgeRatingType matching_rating, second_matching_rating; - - forall_nodes(G, n) { - const path & p = pathset.get_path(n); - - if(not p.is_active()) { - continue; - } - if(p.get_tail() != n) { - continue; - } - if(p.get_length() == 0) { - continue; - } - - if(p.get_head() == p.get_tail()) { - // ******************************** - // handling cycles - // ******************************** - std::vector a_matching, a_second_matching; - std::deque unpacked_cycle; - unpack_path(p, pathset, unpacked_cycle); - - EdgeID first = unpacked_cycle.front(); - unpacked_cycle.pop_front(); - - maximum_weight_matching(G, - unpacked_cycle, - a_matching, - matching_rating); - - unpacked_cycle.push_front(first); - EdgeID last = unpacked_cycle.back(); - unpacked_cycle.pop_back(); - - maximum_weight_matching(G, - unpacked_cycle, - a_second_matching, - second_matching_rating); - - unpacked_cycle.push_back(last); - - if(matching_rating > second_matching_rating) { - //apply first matching - apply_matching(G, a_matching, sources, edge_matching); - } else { - //apply second matching - apply_matching(G, a_second_matching, sources, edge_matching); - } - } else { - // ******************************** - // handling paths - // ******************************** - std::vector a_matching; - std::vector unpacked_path; - - if(p.get_length() == 1) { - //match them directly - EdgeID e = 0; - if(pathset.next_vertex(p.get_tail()) == p.get_head()) { - e = pathset.edge_to_next(p.get_tail()); - } else { - e = pathset.edge_to_prev(p.get_tail()); - ASSERT_TRUE( pathset.prev_vertex(p.get_tail()) == p.get_head() ); - } - - NodeID source = sources[e]; - NodeID target = G.getEdgeTarget(e); - - edge_matching[source] = target; - edge_matching[target] = source; - - continue; - } - unpack_path(p, pathset, unpacked_path); - //dump_unpacked_path(G, unpacked_path, sources); - - EdgeRatingType final_rating = 0; - maximum_weight_matching(G, unpacked_path, a_matching, final_rating); - - //apply matched edges - apply_matching(G, a_matching, sources, edge_matching); - } - } endfor + // extract the paths in the path set into lists of edges. + // then, apply the dynamic programming max weight function to them. Apply + // the matched edges. + EdgeRatingType matching_rating, second_matching_rating; + + forall_nodes(G, n) { + const path & p = pathset.get_path(n); + + if(not p.is_active()) { + continue; + } + if(p.get_tail() != n) { + continue; + } + if(p.get_length() == 0) { + continue; + } + + if(p.get_head() == p.get_tail()) { + // ******************************** + // handling cycles + // ******************************** + std::vector a_matching, a_second_matching; + std::deque unpacked_cycle; + unpack_path(p, pathset, unpacked_cycle); + + EdgeID first = unpacked_cycle.front(); + unpacked_cycle.pop_front(); + + maximum_weight_matching(G, + unpacked_cycle, + a_matching, + matching_rating); + + unpacked_cycle.push_front(first); + EdgeID last = unpacked_cycle.back(); + unpacked_cycle.pop_back(); + + maximum_weight_matching(G, + unpacked_cycle, + a_second_matching, + second_matching_rating); + + unpacked_cycle.push_back(last); + + if(matching_rating > second_matching_rating) { + //apply first matching + apply_matching(G, a_matching, sources, edge_matching); + } else { + //apply second matching + apply_matching(G, a_second_matching, sources, edge_matching); + } + } else { + // ******************************** + // handling paths + // ******************************** + std::vector a_matching; + std::vector unpacked_path; + + if(p.get_length() == 1) { + //match them directly + EdgeID e = 0; + if(pathset.next_vertex(p.get_tail()) == p.get_head()) { + e = pathset.edge_to_next(p.get_tail()); + } else { + e = pathset.edge_to_prev(p.get_tail()); + ASSERT_TRUE( pathset.prev_vertex(p.get_tail()) == p.get_head() ); + } + + NodeID source = sources[e]; + NodeID target = G.getEdgeTarget(e); + + edge_matching[source] = target; + edge_matching[target] = source; + + continue; + } + unpack_path(p, pathset, unpacked_path); + //dump_unpacked_path(G, unpacked_path, sources); + + EdgeRatingType final_rating = 0; + maximum_weight_matching(G, unpacked_path, a_matching, final_rating); + + //apply matched edges + apply_matching(G, a_matching, sources, edge_matching); + } + } endfor } void gpa_matching::apply_matching(graph_access & G, - std::vector & matched_edges, + std::vector & matched_edges, std::vector & sources, Matching & edge_matching) { - //apply matched edges - for( unsigned i = 0; i < matched_edges.size(); i++) { - EdgeID e = matched_edges[i]; - NodeID source = sources[e]; - NodeID target = G.getEdgeTarget(e); + //apply matched edges + for( unsigned i = 0; i < matched_edges.size(); i++) { + EdgeID e = matched_edges[i]; + NodeID source = sources[e]; + NodeID target = G.getEdgeTarget(e); - edge_matching[source] = target; - edge_matching[target] = source; - } + edge_matching[source] = target; + edge_matching[target] = source; + } } -template -void gpa_matching::unpack_path(const path & p, - const path_set & pathset, +template +void gpa_matching::unpack_path(const path & p, + const path_set & pathset, VectorOrDeque & unpacked_path ) { - NodeID head = p.get_head(); - NodeID prev = p.get_tail(); - NodeID next; - NodeID current = prev; - - if(prev == head) { - //special case: the given path is a cycle - current = pathset.next_vertex(prev); - unpacked_path.push_back(pathset.edge_to_next(prev)); - } - - while(current != head) { - if(pathset.next_vertex(current) == prev) { - next = pathset.prev_vertex(current); - unpacked_path.push_back(pathset.edge_to_prev(current)); - } else { - next = pathset.next_vertex(current); - unpacked_path.push_back(pathset.edge_to_next(current)); - } - prev = current; - current = next; - } + NodeID head = p.get_head(); + NodeID prev = p.get_tail(); + NodeID next; + NodeID current = prev; + + if(prev == head) { + //special case: the given path is a cycle + current = pathset.next_vertex(prev); + unpacked_path.push_back(pathset.edge_to_next(prev)); + } + + while(current != head) { + if(pathset.next_vertex(current) == prev) { + next = pathset.prev_vertex(current); + unpacked_path.push_back(pathset.edge_to_prev(current)); + } else { + next = pathset.next_vertex(current); + unpacked_path.push_back(pathset.edge_to_next(current)); + } + prev = current; + current = next; + } } -template +template void gpa_matching::maximum_weight_matching( graph_access & G, - VectorOrDeque & unpacked_path, + VectorOrDeque & unpacked_path, std::vector & matched_edges, EdgeRatingType & final_rating) { - unsigned k = unpacked_path.size(); - if( k == 1 ) { - matched_edges.push_back(unpacked_path[0]); - return; - } - - std::vector ratings(k, 0.0); - std::vector< bool > decision(k, false); - - ratings[0] = G.getEdgeRating(unpacked_path[0]); - ratings[1] = G.getEdgeRating(unpacked_path[1]); - - decision[0] = true; - if(ratings[0] < ratings[1]) { - decision[1] = true; - } - //build up the decision vector - for( EdgeID i = 2; i < k; i++) { - ASSERT_TRUE(unpacked_path[i] < G.number_of_edges()); - EdgeRatingType curRating = G.getEdgeRating(unpacked_path[i]); - if( curRating + ratings[i-2] > ratings[i-1] ) { - decision[i] = true; - ratings[i] = curRating + ratings[i-2]; - } else { - decision[i] = false; - ratings[i] = ratings[i-1]; - } - } - - if(decision[k-1]) { - final_rating = ratings[k-1]; - } else { - final_rating = ratings[k-2]; - } - //construct optimal solution - for(int i = k-1; i >= 0;) { - if(decision[i]) { - matched_edges.push_back(unpacked_path[i]); - i-=2; - } else { - i-=1; - } - } -} + unsigned k = unpacked_path.size(); + if( k == 1 ) { + matched_edges.push_back(unpacked_path[0]); + return; + } + + std::vector ratings(k, 0.0); + std::vector< bool > decision(k, false); + + ratings[0] = G.getEdgeRating(unpacked_path[0]); + ratings[1] = G.getEdgeRating(unpacked_path[1]); + + decision[0] = true; + if(ratings[0] < ratings[1]) { + decision[1] = true; + } + //build up the decision vector + for( EdgeID i = 2; i < k; i++) { + ASSERT_TRUE(unpacked_path[i] < G.number_of_edges()); + EdgeRatingType curRating = G.getEdgeRating(unpacked_path[i]); + if( curRating + ratings[i-2] > ratings[i-1] ) { + decision[i] = true; + ratings[i] = curRating + ratings[i-2]; + } else { + decision[i] = false; + ratings[i] = ratings[i-1]; + } + } + + if(decision[k-1]) { + final_rating = ratings[k-1]; + } else { + final_rating = ratings[k-2]; + } + //construct optimal solution + for(int i = k-1; i >= 0;) { + if(decision[i]) { + matched_edges.push_back(unpacked_path[i]); + i-=2; + } else { + i-=1; + } + } +} -template +template void gpa_matching::dump_unpacked_path( graph_access & G, VectorOrDeque & unpacked_path, std::vector& sources) { - //dump the path - for( unsigned i = 0; i < unpacked_path.size(); i++) { - EdgeID e = unpacked_path[i]; - std::cout << "(" << sources[e] << " " << G.getEdgeTarget(e) << ") "; - } - std::cout << std::endl; + //dump the path + for( unsigned i = 0; i < unpacked_path.size(); i++) { + EdgeID e = unpacked_path[i]; + std::cout << "(" << sources[e] << " " << G.getEdgeTarget(e) << ") "; + } + std::cout << std::endl; } +} \ No newline at end of file diff --git a/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/gpa_matching.h b/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/gpa_matching.h index 7182c348..87c9e759 100644 --- a/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/gpa_matching.h +++ b/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/gpa_matching.h @@ -11,53 +11,53 @@ #include "coarsening/matching/matching.h" #include "path.h" #include "path_set.h" - +namespace kahip::modified { class gpa_matching : public matching{ - public: - gpa_matching( ); - virtual ~gpa_matching(); - - void match(const PartitionConfig & config, - graph_access & G, - Matching & _matching, - CoarseMapping & coarse_mapping, - NodeID & no_of_coarse_vertices, - NodePermutationMap & permutation); - private: - void init(graph_access & G, - const PartitionConfig & partition_config, - NodePermutationMap & permutation, - Matching & edge_matching, - std::vector & edge_permutation, - std::vector & sources); - - void extract_paths_apply_matching( graph_access & G, - std::vector & sources, - Matching & edge_matching, - path_set & pathset); - - template - void unpack_path(const path & the_path, - const path_set & pathset, - VectorOrDeque & a_path); - - template - void maximum_weight_matching( graph_access & G, - VectorOrDeque & unpacked_path, - std::vector & matched_edges, - EdgeRatingType & final_rating); - - void apply_matching( graph_access & G, - std::vector & matched_edges, - std::vector & sources, - Matching & edge_matching); - - - template - void dump_unpacked_path( graph_access & G, - VectorOrDeque & unpacked_path, - std::vector& sources); +public: + gpa_matching( ); + virtual ~gpa_matching(); + + void match(const PartitionConfig & config, + graph_access & G, + Matching & _matching, + CoarseMapping & coarse_mapping, + NodeID & no_of_coarse_vertices, + NodePermutationMap & permutation); +private: + void init(graph_access & G, + const PartitionConfig & partition_config, + NodePermutationMap & permutation, + Matching & edge_matching, + std::vector & edge_permutation, + std::vector & sources); + + void extract_paths_apply_matching( graph_access & G, + std::vector & sources, + Matching & edge_matching, + path_set & pathset); + + template + void unpack_path(const path & the_path, + const path_set & pathset, + VectorOrDeque & a_path); + + template + void maximum_weight_matching( graph_access & G, + VectorOrDeque & unpacked_path, + std::vector & matched_edges, + EdgeRatingType & final_rating); + + void apply_matching( graph_access & G, + std::vector & matched_edges, + std::vector & sources, + Matching & edge_matching); + + + template + void dump_unpacked_path( graph_access & G, + VectorOrDeque & unpacked_path, + std::vector& sources); }; - +} #endif /* end of include guard: GPA_MATCHING_NXLQ0SIT */ diff --git a/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/path.cpp b/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/path.cpp index 9952605d..79ff28f4 100644 --- a/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/path.cpp +++ b/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/path.cpp @@ -6,7 +6,7 @@ *****************************************************************************/ #include "path.h" - +namespace kahip::modified { path::path() : head(UNDEFINED_NODE), tail(UNDEFINED_NODE), length(0), active(false) { } @@ -18,4 +18,4 @@ path::path(const NodeID & v) : head(v), tail(v), length(0), active(true) { path::~path() { } - +} diff --git a/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/path.h b/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/path.h index 7a7b530f..6ad27831 100644 --- a/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/path.h +++ b/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/path.h @@ -9,45 +9,45 @@ #define PATH_X5LQS3DT #include "definitions.h" - +namespace kahip::modified { class path { - public: - path( ); - path( const NodeID & v ); - virtual ~path(); +public: + path( ); + path( const NodeID & v ); + virtual ~path(); + + void init(const NodeID & v); + + NodeID get_tail() const; + void set_tail(const NodeID & id); - void init(const NodeID & v); + NodeID get_head() const; + void set_head(const NodeID & id); - NodeID get_tail() const; - void set_tail(const NodeID & id); + void set_length(const EdgeID & length); + EdgeID get_length() const; - NodeID get_head() const; - void set_head(const NodeID & id); + //returns wether the given node is an endpoint of the path + bool is_endpoint(const NodeID & id) const; - void set_length(const EdgeID & length); - EdgeID get_length() const; + //returns wether the path is a cycle or not. + bool is_cycle() const; - //returns wether the given node is an endpoint of the path - bool is_endpoint(const NodeID & id) const; - - //returns wether the path is a cycle or not. - bool is_cycle() const; + bool is_active() const; + void set_active(const bool active); - bool is_active() const; - void set_active(const bool active); +private: + //Last vertex of the path. Cycles have head == tail + NodeID head; - private: - //Last vertex of the path. Cycles have head == tail - NodeID head; + //First vertex of the path. Cycles have head == tail + NodeID tail; - //First vertex of the path. Cycles have head == tail - NodeID tail; + //Number of edges in the graph + EdgeID length; - //Number of edges in the graph - EdgeID length; - - // True iff the parth is still in use. False iff it has been removed. - bool active; + // True iff the parth is still in use. False iff it has been removed. + bool active; }; @@ -97,6 +97,6 @@ inline bool path::is_active() const { inline void path::set_active(const bool act) { active = act; } - +} #endif /* end of include guard: PATH_X5LQS3DT */ diff --git a/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/path_set.cpp b/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/path_set.cpp index 13bef2e9..0c04aa23 100644 --- a/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/path_set.cpp +++ b/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/path_set.cpp @@ -6,7 +6,7 @@ *****************************************************************************/ #include "path_set.h" - +namespace kahip::modified { path_set::path_set( graph_access * G_, const PartitionConfig * config_ ): pG(G_), config(config_), m_no_of_paths(pG->number_of_nodes()), m_vertex_to_path(m_no_of_paths), @@ -16,17 +16,17 @@ path_set::path_set( graph_access * G_, const PartitionConfig * config_ ): pG(G_) m_next_edge(m_no_of_paths, UNDEFINED_EDGE), m_prev_edge(m_no_of_paths, UNDEFINED_EDGE) { - graph_access & G = *pG; - forall_nodes(G, node) { - m_paths[node].init(node); - m_vertex_to_path[node] = node; - m_next[node] = node; - m_prev[node] = node; - } endfor + graph_access & G = *pG; + forall_nodes(G, node) { + m_paths[node].init(node); + m_vertex_to_path[node] = node; + m_next[node] = node; + m_prev[node] = node; + } endfor } path_set::~path_set() { } - +} diff --git a/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/path_set.h b/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/path_set.h index 05591372..524faee3 100644 --- a/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/path_set.h +++ b/parallel/modified_kahip/lib/partition/coarsening/matching/gpa/path_set.h @@ -12,75 +12,75 @@ #include "macros_assertions.h" #include "partition_config.h" #include "path.h" - +namespace kahip::modified { class path_set { - public: +public: - path_set( graph_access * G, const PartitionConfig * config ); - virtual ~path_set(); + path_set( graph_access * G, const PartitionConfig * config ); + virtual ~path_set(); - //returns the path that v lies on iff v is an endpoint - const path& get_path(const NodeID & v) const; + //returns the path that v lies on iff v is an endpoint + const path& get_path(const NodeID & v) const; - //returns the number of paths in the set - PathID path_count() const; + //returns the number of paths in the set + PathID path_count() const; - // add the edge with given id to the path set if it is applicable - // returns true iff the edge was applicable - bool add_if_applicable(const NodeID & source, const EdgeID & e); + // add the edge with given id to the path set if it is applicable + // returns true iff the edge was applicable + bool add_if_applicable(const NodeID & source, const EdgeID & e); - //********** - //Navigation - //********** + //********** + //Navigation + //********** - //returns the if of vertex next to v on the path - NodeID next_vertex( const NodeID & v ) const; + //returns the if of vertex next to v on the path + NodeID next_vertex( const NodeID & v ) const; - //returns the if of vertex previous to v on the path - NodeID prev_vertex( const NodeID & v ) const; + //returns the if of vertex previous to v on the path + NodeID prev_vertex( const NodeID & v ) const; - //returns the id of the edge to the next vertex on the path - EdgeID edge_to_next(const NodeID & v) const; + //returns the id of the edge to the next vertex on the path + EdgeID edge_to_next(const NodeID & v) const; - //returns the id of the edge to the previous vertex on the path - EdgeID edge_to_prev(const NodeID & v) const; - private: - graph_access * pG; + //returns the id of the edge to the previous vertex on the path + EdgeID edge_to_prev(const NodeID & v) const; +private: + graph_access * pG; - const PartitionConfig * config; + const PartitionConfig * config; - // Number of Paths - PathID m_no_of_paths; + // Number of Paths + PathID m_no_of_paths; - // for every vertex v, vertex_to_path[v] is the id of the path - std::vector m_vertex_to_path; + // for every vertex v, vertex_to_path[v] is the id of the path + std::vector m_vertex_to_path; - // for every path id p, paths[p] is the path for this id - std::vector m_paths; + // for every path id p, paths[p] is the path for this id + std::vector m_paths; - // for every vertex v, next[v] is the id of the vertex that is next on its path. - // for the head v of a path, next[v] == v - std::vector m_next; + // for every vertex v, next[v] is the id of the vertex that is next on its path. + // for the head v of a path, next[v] == v + std::vector m_next; - // for every vertex v, prev[v] is the id of the vertex that is previouson its path. - // for the tail v of a path, prev[v] == v - std::vector m_prev; + // for every vertex v, prev[v] is the id of the vertex that is previouson its path. + // for the tail v of a path, prev[v] == v + std::vector m_prev; - // for every vertex v, next_edge[v] is the id of the vertex that is used to - // connect the vertex v to the next vertex in the path. - // if next[v] == v the next_edge[v] = UNDEFINED_EDGE - std::vector m_next_edge; + // for every vertex v, next_edge[v] is the id of the vertex that is used to + // connect the vertex v to the next vertex in the path. + // if next[v] == v the next_edge[v] = UNDEFINED_EDGE + std::vector m_next_edge; - // for every vertex v, prev_edge[v] is the id of the vertex that is used to - // connect the vertex v to the previous vertex in the path. - // if prev[v] == v the prev_edge[v] = UNDEFINED_EDGE - std::vector m_prev_edge; + // for every vertex v, prev_edge[v] is the id of the vertex that is used to + // connect the vertex v to the previous vertex in the path. + // if prev[v] == v the prev_edge[v] = UNDEFINED_EDGE + std::vector m_prev_edge; - inline bool is_endpoint(const NodeID & v) const { - return (m_next[v] == v or m_prev[v] == v); - } + inline bool is_endpoint(const NodeID & v) const { + return (m_next[v] == v or m_prev[v] == v); + } }; @@ -96,11 +96,11 @@ inline PathID path_set::path_count() const { inline NodeID path_set::next_vertex( const NodeID & v ) const { return m_next[v]; -} +} inline NodeID path_set::prev_vertex( const NodeID & v ) const { return m_prev[v]; -} +} inline EdgeID path_set::edge_to_next(const NodeID & v) const { return m_next_edge[v]; @@ -119,17 +119,17 @@ inline bool path_set::add_if_applicable(const NodeID & source, const EdgeID & e) // in this case we only grow paths inside blocks if(G.getPartitionIndex(source) != G.getPartitionIndex(target)) return false; - + if(config->combine) { if(G.getSecondPartitionIndex(source) != G.getSecondPartitionIndex(target)) { return false; } } - + } - PathID sourcePathID = m_vertex_to_path[source]; - PathID targetPathID = m_vertex_to_path[target]; + PathID sourcePathID = m_vertex_to_path[source]; + PathID targetPathID = m_vertex_to_path[target]; ASSERT_NEQ(source, target); @@ -143,8 +143,8 @@ inline bool path_set::add_if_applicable(const NodeID & source, const EdgeID & e) ASSERT_TRUE(source_path.is_active()); ASSERT_TRUE(target_path.is_active()); - - if(source_path.is_cycle() or target_path.is_cycle()) { + + if(source_path.is_cycle() or target_path.is_cycle()) { // if one of the paths is a cycle then it is not applicable return false; } @@ -223,7 +223,7 @@ inline bool path_set::add_if_applicable(const NodeID & source, const EdgeID & e) return true; } return false; -} - +} +} #endif /* end of include guard: PATH_SET_80E9CQT1 */ diff --git a/parallel/modified_kahip/lib/partition/coarsening/matching/matching.cpp b/parallel/modified_kahip/lib/partition/coarsening/matching/matching.cpp index 1290a04b..3aae63f2 100644 --- a/parallel/modified_kahip/lib/partition/coarsening/matching/matching.cpp +++ b/parallel/modified_kahip/lib/partition/coarsening/matching/matching.cpp @@ -6,7 +6,7 @@ *****************************************************************************/ #include "matching.h" - +namespace kahip::modified { matching::matching() { } @@ -16,9 +16,9 @@ matching::~matching() { } void matching::print_matching(FILE * out, Matching & edge_matching) { - for (NodeID n = 0; n < edge_matching.size(); n++) { - fprintf(out, "%d:%d\n", n, edge_matching[n]); - } + for (NodeID n = 0; n < edge_matching.size(); n++) { + fprintf(out, "%d:%d\n", n, edge_matching[n]); + } +} } - diff --git a/parallel/modified_kahip/lib/partition/coarsening/matching/matching.h b/parallel/modified_kahip/lib/partition/coarsening/matching/matching.h index a8e6f57c..694536cd 100644 --- a/parallel/modified_kahip/lib/partition/coarsening/matching/matching.h +++ b/parallel/modified_kahip/lib/partition/coarsening/matching/matching.h @@ -10,20 +10,20 @@ #include "data_structure/graph_access.h" #include "partition_config.h" - +namespace kahip::modified { class matching { - public: - matching(); - virtual ~matching(); +public: + matching(); + virtual ~matching(); - virtual void match(const PartitionConfig & partition_config, - graph_access & G, - Matching & _matching, - CoarseMapping & mapping, - NodeID & no_of_coarse_vertices, - NodePermutationMap & permutation) = 0; + virtual void match(const PartitionConfig & partition_config, + graph_access & G, + Matching & _matching, + CoarseMapping & mapping, + NodeID & no_of_coarse_vertices, + NodePermutationMap & permutation) = 0; - void print_matching(FILE * out, Matching & edge_matching); + void print_matching(FILE * out, Matching & edge_matching); }; - +} #endif /* end of include guard: MATCHING_QL4RUO3D */ diff --git a/parallel/modified_kahip/lib/partition/coarsening/matching/random_matching.cpp b/parallel/modified_kahip/lib/partition/coarsening/matching/random_matching.cpp index 3ec73ffe..5788f718 100644 --- a/parallel/modified_kahip/lib/partition/coarsening/matching/random_matching.cpp +++ b/parallel/modified_kahip/lib/partition/coarsening/matching/random_matching.cpp @@ -8,7 +8,7 @@ #include "macros_assertions.h" #include "random_functions.h" #include "random_matching.h" - +namespace kahip::modified { random_matching::random_matching() { } @@ -25,92 +25,93 @@ void random_matching::match(const PartitionConfig & partition_config, NodePermutationMap & permutation) { - permutation.resize(G.number_of_nodes()); - edge_matching.resize(G.number_of_nodes()); - coarse_mapping.resize(G.number_of_nodes()); - - no_of_coarse_vertices = 0; + permutation.resize(G.number_of_nodes()); + edge_matching.resize(G.number_of_nodes()); + coarse_mapping.resize(G.number_of_nodes()); + + no_of_coarse_vertices = 0; + + if(!(partition_config.matching_type == MATCHING_RANDOM_GPA)) { + random_functions::permutate_entries(partition_config, permutation, true); + } else { + for( unsigned int i = 0; i < permutation.size(); i++) { + permutation[i] = i; + } + } + + forall_nodes(G, n) { + edge_matching[n] = n; + } endfor + + if(partition_config.graph_allready_partitioned) { //in this case edges between partitions arent matched + forall_nodes(G, n) { + NodeID curNode = permutation[n]; + NodeWeight curNodeWeight = G.getNodeWeight(curNode); + + if(edge_matching[curNode] == curNode) { + //match with a random neighbor + int matchingPartner = curNode; + forall_out_edges(G, e, curNode) { + NodeID target = G.getEdgeTarget(e); + NodeWeight coarser_weight = G.getNodeWeight(target) + curNodeWeight; + + if(edge_matching[target] == target + && coarser_weight <= partition_config.max_vertex_weight) { + if(G.getPartitionIndex(curNode) != G.getPartitionIndex(target)) + continue; + + if(partition_config.combine) { + if(G.getSecondPartitionIndex(curNode) != G.getSecondPartitionIndex(target)) + continue; + } + + matchingPartner = target; + ASSERT_NEQ(curNode, target); + break; + } + } endfor - if(!(partition_config.matching_type == MATCHING_RANDOM_GPA)) { - random_functions::permutate_entries(partition_config, permutation, true); - } else { - for( unsigned int i = 0; i < permutation.size(); i++) { - permutation[i] = i; - } + coarse_mapping[matchingPartner] = no_of_coarse_vertices; + coarse_mapping[curNode] = no_of_coarse_vertices; + + edge_matching[matchingPartner] = curNode; + edge_matching[curNode] = matchingPartner; + + no_of_coarse_vertices++; + } + } endfor +} else { + //copy n paste from the first if clause but this time all edges are matchable + forall_nodes(G, n) { + NodeID curNode = permutation[n]; + NodeWeight curNodeWeight = G.getNodeWeight(curNode); + + if(edge_matching[curNode] == curNode) { + //match with a random neighbor + int matchingPartner = curNode; + forall_out_edges(G, e, curNode) { + NodeID target = G.getEdgeTarget(e); + NodeWeight coarser_weight = G.getNodeWeight(target) + curNodeWeight; + + if(edge_matching[target] == target + && coarser_weight <= partition_config.max_vertex_weight) { + matchingPartner = target; + ASSERT_NEQ(curNode, target); + break; } + } endfor - forall_nodes(G, n) { - edge_matching[n] = n; - } endfor + coarse_mapping[matchingPartner] = no_of_coarse_vertices; + coarse_mapping[curNode] = no_of_coarse_vertices; - if(partition_config.graph_allready_partitioned) { //in this case edges between partitions arent matched - forall_nodes(G, n) { - NodeID curNode = permutation[n]; - NodeWeight curNodeWeight = G.getNodeWeight(curNode); - - if(edge_matching[curNode] == curNode) { - //match with a random neighbor - int matchingPartner = curNode; - forall_out_edges(G, e, curNode) { - NodeID target = G.getEdgeTarget(e); - NodeWeight coarser_weight = G.getNodeWeight(target) + curNodeWeight; - - if(edge_matching[target] == target - && coarser_weight <= partition_config.max_vertex_weight) { - if(G.getPartitionIndex(curNode) != G.getPartitionIndex(target)) - continue; - - if(partition_config.combine) { - if(G.getSecondPartitionIndex(curNode) != G.getSecondPartitionIndex(target)) - continue; - } - - matchingPartner = target; - ASSERT_NEQ(curNode, target); - break; - } - } endfor - - coarse_mapping[matchingPartner] = no_of_coarse_vertices; - coarse_mapping[curNode] = no_of_coarse_vertices; - - edge_matching[matchingPartner] = curNode; - edge_matching[curNode] = matchingPartner; - - no_of_coarse_vertices++; - } - } endfor - } else { - //copy n paste from the first if clause but this time all edges are matchable - forall_nodes(G, n) { - NodeID curNode = permutation[n]; - NodeWeight curNodeWeight = G.getNodeWeight(curNode); - - if(edge_matching[curNode] == curNode) { - //match with a random neighbor - int matchingPartner = curNode; - forall_out_edges(G, e, curNode) { - NodeID target = G.getEdgeTarget(e); - NodeWeight coarser_weight = G.getNodeWeight(target) + curNodeWeight; - - if(edge_matching[target] == target - && coarser_weight <= partition_config.max_vertex_weight) { - matchingPartner = target; - ASSERT_NEQ(curNode, target); - break; - } - } endfor - - coarse_mapping[matchingPartner] = no_of_coarse_vertices; - coarse_mapping[curNode] = no_of_coarse_vertices; - - edge_matching[matchingPartner] = curNode; - edge_matching[curNode] = matchingPartner; - - no_of_coarse_vertices++; - } - } endfor + edge_matching[matchingPartner] = curNode; + edge_matching[curNode] = matchingPartner; - } - PRINT(std::cout << "log>" << "no of coarse nodes: " << no_of_coarse_vertices << std::endl;) + no_of_coarse_vertices++; + } + } endfor + +} + PRINT(std::cout << "log>" << "no of coarse nodes: " << no_of_coarse_vertices << std::endl;) } +} \ No newline at end of file diff --git a/parallel/modified_kahip/lib/partition/coarsening/matching/random_matching.h b/parallel/modified_kahip/lib/partition/coarsening/matching/random_matching.h index 5e7a2b6c..e04c4785 100644 --- a/parallel/modified_kahip/lib/partition/coarsening/matching/random_matching.h +++ b/parallel/modified_kahip/lib/partition/coarsening/matching/random_matching.h @@ -10,18 +10,18 @@ #define RANDOM_MATCHING_D5YDSMDW #include "matching.h" - +namespace kahip::modified { class random_matching : public matching { - public: - random_matching(); - virtual ~random_matching(); +public: + random_matching(); + virtual ~random_matching(); - void match(const PartitionConfig & config, - graph_access & G, - Matching & _matching, - CoarseMapping & coarse_mapping, - NodeID & no_of_coarse_vertices, - NodePermutationMap & permutation); + void match(const PartitionConfig & config, + graph_access & G, + Matching & _matching, + CoarseMapping & coarse_mapping, + NodeID & no_of_coarse_vertices, + NodePermutationMap & permutation); }; - +} #endif /* end of include guard: RANDOM_MATCHING_D5YDSMDW */ diff --git a/parallel/modified_kahip/lib/partition/coarsening/stop_rules/stop_rules.h b/parallel/modified_kahip/lib/partition/coarsening/stop_rules/stop_rules.h index 72d9b615..0c2a0a1b 100644 --- a/parallel/modified_kahip/lib/partition/coarsening/stop_rules/stop_rules.h +++ b/parallel/modified_kahip/lib/partition/coarsening/stop_rules/stop_rules.h @@ -8,33 +8,33 @@ #ifndef STOP_RULES_SZ45JQS6 #define STOP_RULES_SZ45JQS6 -#include +#include #include "partition_config.h" - +namespace kahip::modified { class stop_rule { - public: - stop_rule() {}; - virtual ~stop_rule() {}; - virtual bool stop( NodeID number_of_finer_vertices, NodeID number_of_coarser_vertices ) = 0; +public: + stop_rule() {}; + virtual ~stop_rule() {}; + virtual bool stop( NodeID number_of_finer_vertices, NodeID number_of_coarser_vertices ) = 0; }; class simple_stop_rule : public stop_rule { - public: - simple_stop_rule(PartitionConfig & config, NodeID number_of_nodes) { - double x = 60; - num_stop = std::max(number_of_nodes/(2.0*x*config.k), 60.0*config.k); - if(config.disable_max_vertex_weight_constraint) { - config.max_vertex_weight = config.upper_bound_partition; - } else { - config.max_vertex_weight = (NodeWeight)(1.5*config.largest_graph_weight/num_stop); - } - }; - virtual ~simple_stop_rule() {}; - bool stop( NodeID number_of_finer_vertices, NodeID number_of_coarser_vertices ); - - private: - NodeID num_stop; +public: + simple_stop_rule(PartitionConfig & config, NodeID number_of_nodes) { + double x = 60; + num_stop = std::max(number_of_nodes/(2.0*x*config.k), 60.0*config.k); + if(config.disable_max_vertex_weight_constraint) { + config.max_vertex_weight = config.upper_bound_partition; + } else { + config.max_vertex_weight = (NodeWeight)(1.5*config.largest_graph_weight/num_stop); + } + }; + virtual ~simple_stop_rule() {}; + bool stop( NodeID number_of_finer_vertices, NodeID number_of_coarser_vertices ); + +private: + NodeID num_stop; }; inline bool simple_stop_rule::stop(NodeID no_of_finer_vertices, NodeID no_of_coarser_vertices ) { @@ -42,16 +42,16 @@ inline bool simple_stop_rule::stop(NodeID no_of_finer_vertices, NodeID no_of_coa return contraction_rate >= 1.1 && no_of_coarser_vertices >= num_stop; } class strong_stop_rule : public stop_rule { - public: - strong_stop_rule(PartitionConfig & config, NodeID number_of_nodes) { - num_stop = config.k; - config.max_vertex_weight = config.upper_bound_partition; - }; - virtual ~strong_stop_rule() {}; - bool stop( NodeID number_of_finer_vertices, NodeID number_of_coarser_vertices ); - - private: - NodeID num_stop; +public: + strong_stop_rule(PartitionConfig & config, NodeID number_of_nodes) { + num_stop = config.k; + config.max_vertex_weight = config.upper_bound_partition; + }; + virtual ~strong_stop_rule() {}; + bool stop( NodeID number_of_finer_vertices, NodeID number_of_coarser_vertices ); + +private: + NodeID num_stop; }; inline bool strong_stop_rule::stop(NodeID no_of_finer_vertices, NodeID no_of_coarser_vertices ) { @@ -60,34 +60,34 @@ inline bool strong_stop_rule::stop(NodeID no_of_finer_vertices, NodeID no_of_coa } class multiple_k_stop_rule : public stop_rule { - public: - multiple_k_stop_rule (PartitionConfig & config, NodeID number_of_nodes) { - num_stop = config.num_vert_stop_factor*config.k; +public: + multiple_k_stop_rule (PartitionConfig & config, NodeID number_of_nodes) { + num_stop = config.num_vert_stop_factor*config.k; - if(config.disable_max_vertex_weight_constraint) { - config.max_vertex_weight = config.upper_bound_partition; + if(config.disable_max_vertex_weight_constraint) { + config.max_vertex_weight = config.upper_bound_partition; + } else { + if(config.initial_partitioning) { + //if we perform initial partitioning we relax this constraint + config.max_vertex_weight = 1.5*((double)config.largest_graph_weight)/(2*config.num_vert_stop_factor); } else { - if(config.initial_partitioning) { - //if we perform initial partitioning we relax this constraint - config.max_vertex_weight = 1.5*((double)config.largest_graph_weight)/(2*config.num_vert_stop_factor); - } else { - config.max_vertex_weight = (NodeWeight)(1.5*config.largest_graph_weight/num_stop); - } + config.max_vertex_weight = (NodeWeight)(1.5*config.largest_graph_weight/num_stop); } + } - }; - virtual ~multiple_k_stop_rule () {}; - bool stop( NodeID number_of_finer_vertices, NodeID number_of_coarser_vertices ); + }; + virtual ~multiple_k_stop_rule () {}; + bool stop( NodeID number_of_finer_vertices, NodeID number_of_coarser_vertices ); - private: - NodeID num_stop; +private: + NodeID num_stop; }; inline bool multiple_k_stop_rule::stop(NodeID no_of_finer_vertices, NodeID no_of_coarser_vertices ) { double contraction_rate = 1.0 * no_of_finer_vertices / (double)no_of_coarser_vertices; return contraction_rate >= 1.1 && no_of_coarser_vertices >= num_stop; } - +} diff --git a/parallel/modified_kahip/lib/partition/graph_partitioner.cpp b/parallel/modified_kahip/lib/partition/graph_partitioner.cpp index 104dfe74..4ad2e176 100644 --- a/parallel/modified_kahip/lib/partition/graph_partitioner.cpp +++ b/parallel/modified_kahip/lib/partition/graph_partitioner.cpp @@ -15,217 +15,211 @@ #include "uncoarsening/refinement/mixed_refinement.h" #include "w_cycles/wcycle_partitioner.h" -graph_partitioner::graph_partitioner() { +namespace kahip::modified { +void graph_partitioner::perform_recursive_partitioning(PartitionConfig & config, graph_access & G) { + m_global_k = config.k; + m_global_upper_bound = config.upper_bound_partition; + m_rnd_bal = random_functions::nextDouble(1,2); + perform_recursive_partitioning_internal(config, G, 0, config.k-1); } -graph_partitioner::~graph_partitioner() { +void graph_partitioner::perform_recursive_partitioning_internal(PartitionConfig & config, + graph_access & G, + PartitionID lb, + PartitionID ub) { + G.set_partition_count(2); + + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + // configuration of bipartitioning + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + PartitionConfig bipart_config = config; + bipart_config.k = 2; + bipart_config.stop_rule = STOP_RULE_MULTIPLE_K; + bipart_config.num_vert_stop_factor = 100; + double epsilon = 0; + bipart_config.rebalance = false; + bipart_config.softrebalance = true; + + if(config.k < 64) { + epsilon = m_rnd_bal/100.0; + bipart_config.rebalance = false; + bipart_config.softrebalance = false; + } else { + epsilon = 1/100.0; + } + if(m_global_k == 2) { + epsilon = 3.0/100.0; + } + + + bipart_config.upper_bound_partition = ceil((1+epsilon)*config.largest_graph_weight/(double)bipart_config.k); + bipart_config.corner_refinement_enabled = false; + bipart_config.quotient_graph_refinement_disabled = false; + bipart_config.refinement_scheduling_algorithm = REFINEMENT_SCHEDULING_ACTIVE_BLOCKS; + bipart_config.kway_adaptive_limits_beta = log(G.number_of_nodes()); + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + // end configuration + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + NodeID new_ub_lhs = floor((lb+ub)/2); + NodeID new_lb_rhs = floor((lb+ub)/2+1); + NodeID num_blocks_lhs = new_ub_lhs - lb + 1; + NodeID num_blocks_rhs = ub - new_lb_rhs + 1; + + if(config.k % 2 != 0) { + //otherwise the block weights have to be + bipart_config.target_weights.clear(); + bipart_config.target_weights.push_back((1+epsilon)*num_blocks_lhs/(double)(num_blocks_lhs+num_blocks_rhs)*config.largest_graph_weight); + bipart_config.target_weights.push_back((1+epsilon)*num_blocks_rhs/(double)(num_blocks_lhs+num_blocks_rhs)*config.largest_graph_weight); + bipart_config.initial_bipartitioning = true; + bipart_config.refinement_type = REFINEMENT_TYPE_FM; // flows not supported for odd block weights + } else { + + bipart_config.target_weights.clear(); + bipart_config.target_weights.push_back(bipart_config.upper_bound_partition); + bipart_config.target_weights.push_back(bipart_config.upper_bound_partition); + bipart_config.initial_bipartitioning = false; + } + + bipart_config.grow_target = ceil(num_blocks_lhs/(double)(num_blocks_lhs+num_blocks_rhs)*config.largest_graph_weight); + + perform_partitioning(bipart_config, G); + + if( config.k > 2 ) { + graph_extractor extractor; + + graph_access extracted_block_lhs; + graph_access extracted_block_rhs; + std::vector mapping_extracted_to_G_lhs; // map the new nodes to the nodes in the old graph G + std::vector mapping_extracted_to_G_rhs; // map the new nodes to the nodes in the old graph G + + NodeWeight weight_lhs_block = 0; + NodeWeight weight_rhs_block = 0; + + extractor.extract_two_blocks(G, extracted_block_lhs, + extracted_block_rhs, + mapping_extracted_to_G_lhs, + mapping_extracted_to_G_rhs, + weight_lhs_block, weight_rhs_block); + + PartitionConfig rec_config = config; + if(num_blocks_lhs > 1) { + rec_config.k = num_blocks_lhs; + + rec_config.largest_graph_weight = weight_lhs_block; + perform_recursive_partitioning_internal( rec_config, extracted_block_lhs, lb, new_ub_lhs); + + //apply partition + forall_nodes(extracted_block_lhs, node) { + G.setPartitionIndex(mapping_extracted_to_G_lhs[node], extracted_block_lhs.getPartitionIndex(node)); + } endfor + +} else { + //apply partition + forall_nodes(extracted_block_lhs, node) { + G.setPartitionIndex(mapping_extracted_to_G_lhs[node], lb); + } endfor } -void graph_partitioner::perform_recursive_partitioning(PartitionConfig & config, graph_access & G) { - m_global_k = config.k; - m_global_upper_bound = config.upper_bound_partition; - m_rnd_bal = random_functions::nextDouble(1,2); - perform_recursive_partitioning_internal(config, G, 0, config.k-1); + if(num_blocks_rhs > 1) { + rec_config.k = num_blocks_rhs; + rec_config.largest_graph_weight = weight_rhs_block; + perform_recursive_partitioning_internal( rec_config, extracted_block_rhs, new_lb_rhs, ub); + + forall_nodes(extracted_block_rhs, node) { + G.setPartitionIndex(mapping_extracted_to_G_rhs[node], extracted_block_rhs.getPartitionIndex(node)); + } endfor + +} else { + //apply partition + forall_nodes(extracted_block_rhs, node) { + G.setPartitionIndex(mapping_extracted_to_G_rhs[node], ub); + } endfor } -void graph_partitioner::perform_recursive_partitioning_internal(PartitionConfig & config, - graph_access & G, - PartitionID lb, - PartitionID ub) { + } else { + forall_nodes(G, node) { + if(G.getPartitionIndex(node) == 0) { + G.setPartitionIndex(node, lb); + } else { + G.setPartitionIndex(node, ub); + } + } endfor +} - G.set_partition_count(2); - - // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - // configuration of bipartitioning - // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - PartitionConfig bipart_config = config; - bipart_config.k = 2; - bipart_config.stop_rule = STOP_RULE_MULTIPLE_K; - bipart_config.num_vert_stop_factor = 100; - double epsilon = 0; - bipart_config.rebalance = false; - bipart_config.softrebalance = true; - - if(config.k < 64) { - epsilon = m_rnd_bal/100.0; - bipart_config.rebalance = false; - bipart_config.softrebalance = false; - } else { - epsilon = 1/100.0; - } - if(m_global_k == 2) { - epsilon = 3.0/100.0; - } - - - bipart_config.upper_bound_partition = ceil((1+epsilon)*config.largest_graph_weight/(double)bipart_config.k); - bipart_config.corner_refinement_enabled = false; - bipart_config.quotient_graph_refinement_disabled = false; - bipart_config.refinement_scheduling_algorithm = REFINEMENT_SCHEDULING_ACTIVE_BLOCKS; - bipart_config.kway_adaptive_limits_beta = log(G.number_of_nodes()); - // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - // end configuration - // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - NodeID new_ub_lhs = floor((lb+ub)/2); - NodeID new_lb_rhs = floor((lb+ub)/2+1); - NodeID num_blocks_lhs = new_ub_lhs - lb + 1; - NodeID num_blocks_rhs = ub - new_lb_rhs + 1; - - if(config.k % 2 != 0) { - //otherwise the block weights have to be - bipart_config.target_weights.clear(); - bipart_config.target_weights.push_back((1+epsilon)*num_blocks_lhs/(double)(num_blocks_lhs+num_blocks_rhs)*config.largest_graph_weight); - bipart_config.target_weights.push_back((1+epsilon)*num_blocks_rhs/(double)(num_blocks_lhs+num_blocks_rhs)*config.largest_graph_weight); - bipart_config.initial_bipartitioning = true; - bipart_config.refinement_type = REFINEMENT_TYPE_FM; // flows not supported for odd block weights - } else { - - bipart_config.target_weights.clear(); - bipart_config.target_weights.push_back(bipart_config.upper_bound_partition); - bipart_config.target_weights.push_back(bipart_config.upper_bound_partition); - bipart_config.initial_bipartitioning = false; - } - - bipart_config.grow_target = ceil(num_blocks_lhs/(double)(num_blocks_lhs+num_blocks_rhs)*config.largest_graph_weight); - - perform_partitioning(bipart_config, G); - - if( config.k > 2 ) { - graph_extractor extractor; - - graph_access extracted_block_lhs; - graph_access extracted_block_rhs; - std::vector mapping_extracted_to_G_lhs; // map the new nodes to the nodes in the old graph G - std::vector mapping_extracted_to_G_rhs; // map the new nodes to the nodes in the old graph G - - NodeWeight weight_lhs_block = 0; - NodeWeight weight_rhs_block = 0; - - extractor.extract_two_blocks(G, extracted_block_lhs, - extracted_block_rhs, - mapping_extracted_to_G_lhs, - mapping_extracted_to_G_rhs, - weight_lhs_block, weight_rhs_block); - - PartitionConfig rec_config = config; - if(num_blocks_lhs > 1) { - rec_config.k = num_blocks_lhs; - - rec_config.largest_graph_weight = weight_lhs_block; - perform_recursive_partitioning_internal( rec_config, extracted_block_lhs, lb, new_ub_lhs); - - //apply partition - forall_nodes(extracted_block_lhs, node) { - G.setPartitionIndex(mapping_extracted_to_G_lhs[node], extracted_block_lhs.getPartitionIndex(node)); - } endfor - - } else { - //apply partition - forall_nodes(extracted_block_lhs, node) { - G.setPartitionIndex(mapping_extracted_to_G_lhs[node], lb); - } endfor - } - - if(num_blocks_rhs > 1) { - rec_config.k = num_blocks_rhs; - rec_config.largest_graph_weight = weight_rhs_block; - perform_recursive_partitioning_internal( rec_config, extracted_block_rhs, new_lb_rhs, ub); - - forall_nodes(extracted_block_rhs, node) { - G.setPartitionIndex(mapping_extracted_to_G_rhs[node], extracted_block_rhs.getPartitionIndex(node)); - } endfor - - } else { - //apply partition - forall_nodes(extracted_block_rhs, node) { - G.setPartitionIndex(mapping_extracted_to_G_rhs[node], ub); - } endfor - } - - } else { - forall_nodes(G, node) { - if(G.getPartitionIndex(node) == 0) { - G.setPartitionIndex(node, lb); - } else { - G.setPartitionIndex(node, ub); - } - } endfor - } - - G.set_partition_count(config.k); + G.set_partition_count(config.k); } void graph_partitioner::single_run( PartitionConfig & config, graph_access & G) { - for( unsigned i = 1; i <= config.global_cycle_iterations; i++) { - PRINT(std::cout << "vcycle " << i << " of " << config.global_cycle_iterations << std::endl;) - if(config.use_wcycles || config.use_fullmultigrid) { - wcycle_partitioner w_partitioner; - w_partitioner.perform_partitioning(config, G); - } else { - coarsening coarsen; - initial_partitioning init_part; - uncoarsening uncoarsen; - - graph_hierarchy hierarchy; - - coarsen.perform_coarsening(config, G, hierarchy); - init_part.perform_initial_partitioning(config, hierarchy); - uncoarsen.perform_uncoarsening(config, hierarchy); - } - config.graph_allready_partitioned = true; - config.balance_factor = 0; - } + for( unsigned i = 1; i <= config.global_cycle_iterations; i++) { + PRINT(std::cout << "vcycle " << i << " of " << config.global_cycle_iterations << std::endl;) + if(config.use_wcycles || config.use_fullmultigrid) { + wcycle_partitioner w_partitioner; + w_partitioner.perform_partitioning(config, G); + } else { + coarsening coarsen; + initial_partitioning init_part; + uncoarsening uncoarsen; + + graph_hierarchy hierarchy; + + coarsen.perform_coarsening(config, G, hierarchy); + init_part.perform_initial_partitioning(config, hierarchy); + uncoarsen.perform_uncoarsening(config, hierarchy); + } + config.graph_allready_partitioned = true; + config.balance_factor = 0; + } } void graph_partitioner::perform_partitioning( PartitionConfig & config, graph_access & G) { - if(config.only_first_level) { - if( !config.graph_allready_partitioned) { - initial_partitioning init_part; - init_part.perform_initial_partitioning(config, G); - } - - if( !config.mh_no_mh ) { - complete_boundary boundary(&G); - boundary.build(); - refinement* refine = new mixed_refinement(); - refine->perform_refinement(config, G, boundary); - delete refine; - } - - return; - } - - if( config.repetitions == 1 ) { - single_run(config,G); - } else { - quality_metrics qm; - // currently only for ecosocial - EdgeWeight best_cut = std::numeric_limits< EdgeWeight >::max(); - std::vector< PartitionID > best_map = std::vector< PartitionID >(G.number_of_nodes()); - for( int i = 0; i < config.repetitions; i++) { - forall_nodes(G, node) { - G.setPartitionIndex(node,0); - } endfor - PartitionConfig working_config = config; - single_run(working_config, G); - - EdgeWeight cur_cut = qm.edge_cut(G); - if( cur_cut < best_cut ) { - forall_nodes(G, node) { - best_map[node] = G.getPartitionIndex(node); - } endfor - - best_cut = cur_cut; - } - } - - forall_nodes(G, node) { - G.setPartitionIndex(node, best_map[node]); - } endfor - - } -} + if(config.only_first_level) { + if( !config.graph_allready_partitioned) { + initial_partitioning init_part; + init_part.perform_initial_partitioning(config, G); + } + + if( !config.mh_no_mh ) { + complete_boundary boundary(&G); + boundary.build(); + refinement* refine = new mixed_refinement(); + refine->perform_refinement(config, G, boundary); + delete refine; + } + + return; + } + + if( config.repetitions == 1 ) { + single_run(config,G); + } else { + quality_metrics qm; + // currently only for ecosocial + EdgeWeight best_cut = std::numeric_limits< EdgeWeight >::max(); + std::vector< PartitionID > best_map = std::vector< PartitionID >(G.number_of_nodes()); + for( int i = 0; i < config.repetitions; i++) { + forall_nodes(G, node) { + G.setPartitionIndex(node,0); + } endfor + PartitionConfig working_config = config; + single_run(working_config, G); + + EdgeWeight cur_cut = qm.edge_cut(G); + if( cur_cut < best_cut ) { + forall_nodes(G, node) { + best_map[node] = G.getPartitionIndex(node); + } endfor + + best_cut = cur_cut; + } + } + + forall_nodes(G, node) { + G.setPartitionIndex(node, best_map[node]); + } endfor +} +} +} diff --git a/parallel/modified_kahip/lib/partition/graph_partitioner.h b/parallel/modified_kahip/lib/partition/graph_partitioner.h index 96e458ad..39f1ab17 100644 --- a/parallel/modified_kahip/lib/partition/graph_partitioner.h +++ b/parallel/modified_kahip/lib/partition/graph_partitioner.h @@ -13,24 +13,24 @@ #include "data_structure/graph_access.h" #include "partition_config.h" #include "uncoarsening/refinement/refinement.h" - +namespace kahip::modified { class graph_partitioner { public: - graph_partitioner(); - virtual ~graph_partitioner(); + graph_partitioner() = default; + virtual ~graph_partitioner() = default; - void perform_partitioning(PartitionConfig & graph_partitioner_config, graph_access & G); - void perform_recursive_partitioning(PartitionConfig & graph_partitioner_config, graph_access & G); + void perform_partitioning(PartitionConfig & graph_partitioner_config, graph_access & G); + void perform_recursive_partitioning(PartitionConfig & graph_partitioner_config, graph_access & G); private: - void perform_recursive_partitioning_internal(PartitionConfig & graph_partitioner_config, - graph_access & G, - PartitionID lb, PartitionID ub); - void single_run( PartitionConfig & config, graph_access & G); + void perform_recursive_partitioning_internal(PartitionConfig & graph_partitioner_config, + graph_access & G, + PartitionID lb, PartitionID ub); + void single_run( PartitionConfig & config, graph_access & G); - unsigned m_global_k; - int m_global_upper_bound; - int m_rnd_bal; + unsigned m_global_k; + int m_global_upper_bound; + int m_rnd_bal; }; - +} #endif /* end of include guard: PARTITION_OL9XTLU4 */ diff --git a/parallel/modified_kahip/lib/partition/initial_partitioning/bipartition.cpp b/parallel/modified_kahip/lib/partition/initial_partitioning/bipartition.cpp index 310d70b2..46cf87a7 100644 --- a/parallel/modified_kahip/lib/partition/initial_partitioning/bipartition.cpp +++ b/parallel/modified_kahip/lib/partition/initial_partitioning/bipartition.cpp @@ -5,14 +5,21 @@ * Christian Schulz *****************************************************************************/ +#include +#include +#include +#include +#include + #include "bipartition.h" #include "data_structure/priority_queues/maxNodeHeap.h" +#include "partition/initial_partitioning/bipartition_candidate.h" #include "quality_metrics.h" #include "random_functions.h" #include "timer.h" #include "uncoarsening/refinement/quotient_graph_refinement/quotient_graph_refinement.h" #include "uncoarsening/refinement/refinement.h" - +namespace kahip::modified { bipartition::bipartition() { } @@ -26,273 +33,326 @@ void bipartition::initial_partition( const PartitionConfig & config, graph_access & G, int* partition_map) { - timer t; - t.restart(); - unsigned iterations = config.bipartition_tries; - EdgeWeight best_cut = std::numeric_limits::max(); - int best_load = std::numeric_limits::max(); - - for( unsigned i = 0; i < iterations; i++) { - if(config.bipartition_algorithm == BIPARTITION_BFS) { - grow_regions_bfs(config, G); - } else if( config.bipartition_algorithm == BIPARTITION_FM) { - grow_regions_fm(config, G); - } - - G.set_partition_count(2); - - post_fm(config, G); - - quality_metrics qm; - EdgeWeight curcut = qm.edge_cut(G); - - int lhs_block_weight = 0; - int rhs_block_weight = 0; - - forall_nodes(G, node) { - if(G.getPartitionIndex(node) == 0) { - lhs_block_weight += G.getNodeWeight(node); - } else { - rhs_block_weight += G.getNodeWeight(node); - } - } endfor - - int lhs_overload = std::max(lhs_block_weight - config.target_weights[0],0); - int rhs_overload = std::max(rhs_block_weight - config.target_weights[1],0); - - if(curcut < best_cut || (curcut == best_cut && lhs_overload + rhs_block_weight < best_load) ) { - //store it - best_cut = curcut; - best_load = lhs_overload + rhs_overload; - - forall_nodes(G, n) { - partition_map[n] = G.getPartitionIndex(n); - } endfor - } - - } - PRINT(std::cout << "bipartition took " << t.elapsed() << std::endl;) + timer t; + t.restart(); + auto const targets = + kahip::initial_partitioning::validated_bipartition_targets( + config.target_weights); + if(!targets) { + throw std::invalid_argument( + "bipartition requires two nonnegative target weights"); + } + if(config.bipartition_tries <= 0) { + throw std::invalid_argument( + "bipartition requires at least one candidate"); + } + if(config.bipartition_algorithm != BIPARTITION_BFS && + config.bipartition_algorithm != BIPARTITION_FM) { + throw std::invalid_argument( + "bipartition requires a valid growth algorithm"); + } + + if(G.number_of_nodes() == 0) { + G.set_partition_count(2); + PRINT(std::cout << "bipartition took " << t.elapsed() << std::endl;) + return; + } + + auto const iterations = static_cast(config.bipartition_tries); + auto const requires_two_nonempty_blocks = G.number_of_nodes() >= 2; + std::optional + best_candidate; + std::vector best_partition(G.number_of_nodes()); + + for( unsigned i = 0; i < iterations; i++) { + if(config.bipartition_algorithm == BIPARTITION_BFS) { + grow_regions_bfs(config, G); + } else { + grow_regions_fm(config, G); + } + + G.set_partition_count(2); + + post_fm(config, G); + + quality_metrics qm; + EdgeWeight curcut = qm.edge_cut(G); + + if(curcut < 0) { + throw std::logic_error("bipartition produced a negative edge cut"); + } + + std::uint64_t lhs_block_weight = 0; + std::uint64_t rhs_block_weight = 0; + std::uint64_t lhs_vertices = 0; + std::uint64_t rhs_vertices = 0; + bool partition_ids_are_valid = true; + + forall_nodes(G, node) { + if(G.getPartitionIndex(node) == 0) { + lhs_block_weight += G.getNodeWeight(node); + ++lhs_vertices; + } else if(G.getPartitionIndex(node) == 1) { + rhs_block_weight += G.getNodeWeight(node); + ++rhs_vertices; + } else { + partition_ids_are_valid = false; + } + } endfor + + auto const candidate = + kahip::initial_partitioning::make_bipartition_candidate( + static_cast(curcut), + lhs_block_weight, + rhs_block_weight, + *targets, + lhs_vertices, + rhs_vertices, + partition_ids_are_valid, + requires_two_nonempty_blocks, + i); + if(!best_candidate || + kahip::initial_partitioning::is_better_bipartition_candidate( + candidate, *best_candidate)) { + best_candidate = candidate; + forall_nodes(G, n) { + best_partition[n] = G.getPartitionIndex(n); + } endfor + } + + } + if(!best_candidate || !best_candidate->valid_blocks) { + throw std::runtime_error( + "bipartition failed to produce two valid nonempty blocks"); + } + std::ranges::copy(best_partition, partition_map); + PRINT(std::cout << "bipartition took " << t.elapsed() << std::endl;) } -void bipartition::initial_partition( const PartitionConfig & config, - const unsigned int seed, - graph_access & G, +void bipartition::initial_partition( const PartitionConfig & config, + const unsigned int seed, + graph_access & G, int* xadj, - int* adjncy, - int* vwgt, + int* adjncy, + int* vwgt, int* adjwgt, int* partition_map) { - std::cout << "not implemented yet" << std::endl; + std::cout << "not implemented yet" << std::endl; } void bipartition::post_fm(const PartitionConfig & config, graph_access & G) { - refinement* refine = new quotient_graph_refinement(); - complete_boundary* boundary = new complete_boundary(&G); - boundary->build(); - - PartitionConfig initial_cfg = config; - initial_cfg.fm_search_limit = config.bipartition_post_fm_limits; - initial_cfg.refinement_type = REFINEMENT_TYPE_FM; - initial_cfg.refinement_scheduling_algorithm = REFINEMENT_SCHEDULING_ACTIVE_BLOCKS; - initial_cfg.bank_account_factor = 5; - initial_cfg.rebalance = true; - initial_cfg.softrebalance = true; - initial_cfg.upper_bound_partition = 100000000; - initial_cfg.initial_bipartitioning = true; - refine->perform_refinement(initial_cfg, G, *boundary); - - delete refine; - delete boundary; + refinement* refine = new quotient_graph_refinement(); + complete_boundary* boundary = new complete_boundary(&G); + boundary->build(); + + PartitionConfig initial_cfg = config; + initial_cfg.fm_search_limit = config.bipartition_post_fm_limits; + initial_cfg.refinement_type = REFINEMENT_TYPE_FM; + initial_cfg.refinement_scheduling_algorithm = REFINEMENT_SCHEDULING_ACTIVE_BLOCKS; + initial_cfg.bank_account_factor = 5; + initial_cfg.rebalance = true; + initial_cfg.softrebalance = true; + initial_cfg.upper_bound_partition = 100000000; + initial_cfg.initial_bipartitioning = true; + refine->perform_refinement(initial_cfg, G, *boundary); + + delete refine; + delete boundary; } NodeID bipartition::find_start_node( const PartitionConfig & config, graph_access & G) { - NodeID startNode = random_functions::nextInt(0, G.number_of_nodes()-1); - NodeID lastNode = startNode; - - int counter = G.number_of_nodes(); - while( G.getNodeDegree(startNode) == 0 && --counter > 0) { - startNode = random_functions::nextInt(0, G.number_of_nodes()-1); + NodeID startNode = random_functions::nextInt(0, G.number_of_nodes()-1); + NodeID lastNode = startNode; + + int counter = G.number_of_nodes(); + while( G.getNodeDegree(startNode) == 0 && --counter > 0) { + startNode = random_functions::nextInt(0, G.number_of_nodes()-1); + } + + //now perform a bfs to get a partition + for( unsigned i = 0; i < 3; i++) { + std::vector touched(G.number_of_nodes(), false); + startNode = lastNode; + touched[startNode] = true; + + std::queue* bfsqueue = new std::queue; + bfsqueue->push(startNode); + while(!bfsqueue->empty()) { + NodeID source = bfsqueue->front(); + lastNode = source; + bfsqueue->pop(); + + forall_out_edges(G, e, source) { + NodeID target = G.getEdgeTarget(e); + if(!touched[target]) { + touched[target] = true; + bfsqueue->push(target); } + } endfor +} + delete bfsqueue; - //now perform a bfs to get a partition - for( unsigned i = 0; i < 3; i++) { - std::vector touched(G.number_of_nodes(), false); - startNode = lastNode; - touched[startNode] = true; - - std::queue* bfsqueue = new std::queue; - bfsqueue->push(startNode); - while(!bfsqueue->empty()) { - NodeID source = bfsqueue->front(); - lastNode = source; - bfsqueue->pop(); - - forall_out_edges(G, e, source) { - NodeID target = G.getEdgeTarget(e); - if(!touched[target]) { - touched[target] = true; - bfsqueue->push(target); - } - } endfor - } - delete bfsqueue; - - } - return lastNode; + } + return lastNode; } void bipartition::grow_regions_bfs(const PartitionConfig & config, graph_access & G) { - if(G.number_of_nodes() == 0) return; - - NodeID startNode = random_functions::nextInt(0, G.number_of_nodes()-1); - if(config.buffoon) { startNode = find_start_node(config, G); } // more likely to produce connected partitions - - std::vector touched(G.number_of_nodes(), false); - touched[startNode] = true; - NodeWeight cur_partition_weight = 0; - - forall_nodes(G, node) { - G.setPartitionIndex(node, 1); - } endfor - - NodeID nodes_left = G.number_of_nodes()-1; - - //now perform a bfs to get a partition - std::queue* bfsqueue = new std::queue; - bfsqueue->push(startNode); - for(;;) { - if( nodes_left == 1 ) { - //only one node left --> we have to break - break; - } - - if(bfsqueue->empty() && nodes_left > 0) { - //disconnected graph -> find a new start node among those that havent been touched - NodeID k = random_functions::nextInt(0, nodes_left-1); - NodeID start_node = 0; - forall_nodes(G, node) { - if(!touched[node]) { - if(k == 0) { - if( G.getNodeDegree(node) != 0) { - start_node = node; - break; - } else { - G.setPartitionIndex(node, 0); - nodes_left--; - cur_partition_weight += G.getNodeWeight(node); - touched[node] = true; - - if(cur_partition_weight >= (NodeWeight) config.grow_target) break; - } - } else { - k--; - } - } - } endfor - - if(cur_partition_weight >= (NodeWeight) config.grow_target) break; - - bfsqueue->push(start_node); - touched[start_node] = true; - } else if (bfsqueue->empty() && nodes_left == 0) { - break; - } - - NodeID source = bfsqueue->front(); - bfsqueue->pop(); - G.setPartitionIndex(source, 0); - - nodes_left--; - cur_partition_weight += G.getNodeWeight(source); - - if(cur_partition_weight >= (NodeWeight) config.grow_target) break; - - forall_out_edges(G, e, source) { - NodeID target = G.getEdgeTarget(e); - if(!touched[target]) { - touched[target] = true; - bfsqueue->push(target); - } - } endfor + if(G.number_of_nodes() == 0) return; + + NodeID startNode = random_functions::nextInt(0, G.number_of_nodes()-1); + if(config.buffoon) { startNode = find_start_node(config, G); } // more likely to produce connected partitions + + std::vector touched(G.number_of_nodes(), false); + touched[startNode] = true; + NodeWeight cur_partition_weight = 0; + + forall_nodes(G, node) { + G.setPartitionIndex(node, 1); + } endfor + + // The queued start vertex has not been assigned yet. Count it until it is + // removed from the queue so a two-vertex graph still assigns one vertex to + // each side instead of leaving the initial all-one labeling unchanged. + NodeID nodes_left = G.number_of_nodes(); + + //now perform a bfs to get a partition + std::queue* bfsqueue = new std::queue; + bfsqueue->push(startNode); + for(;;) { + if( nodes_left == 1 ) { + //only one node left --> we have to break + break; + } + + if(bfsqueue->empty() && nodes_left > 0) { + //disconnected graph -> find a new start node among those that havent been touched + NodeID k = random_functions::nextInt(0, nodes_left-1); + NodeID start_node = 0; + forall_nodes(G, node) { + if(!touched[node]) { + if(k == 0) { + if( G.getNodeDegree(node) != 0) { + start_node = node; + break; + } else { + G.setPartitionIndex(node, 0); + nodes_left--; + cur_partition_weight += G.getNodeWeight(node); + touched[node] = true; + + if(cur_partition_weight >= (NodeWeight) config.grow_target) break; + } + } else { + k--; + } } - delete bfsqueue; + } endfor + + if(cur_partition_weight >= (NodeWeight) config.grow_target) break; + + bfsqueue->push(start_node); + touched[start_node] = true; + } else if (bfsqueue->empty() && nodes_left == 0) { + break; + } + + NodeID source = bfsqueue->front(); + bfsqueue->pop(); + G.setPartitionIndex(source, 0); + + nodes_left--; + cur_partition_weight += G.getNodeWeight(source); + + if(cur_partition_weight >= (NodeWeight) config.grow_target) break; + + forall_out_edges(G, e, source) { + NodeID target = G.getEdgeTarget(e); + if(!touched[target]) { + touched[target] = true; + bfsqueue->push(target); + } + } endfor +} + delete bfsqueue; } void bipartition::grow_regions_fm(const PartitionConfig & config, graph_access & G) { - if(G.number_of_nodes() == 0) return; - - //NodeID startNode = random_functions::nextInt(0, G.number_of_nodes()-1); - NodeID startNode = find_start_node(config, G); - - std::vector touched(G.number_of_nodes(), false); - touched[startNode] = true; - NodeWeight cur_partition_weight = 0; - - forall_nodes(G, node) { - G.setPartitionIndex(node, 1); - } endfor - - NodeID nodes_left = G.number_of_nodes()-1; - - //now perform a pseudo dijkstra to get a partition - maxNodeHeap* queue = new maxNodeHeap(); - queue->insert(startNode, 0); // in this case the gain doesn't really matter - - for(;;) { - if( nodes_left == 1 ) { - //only one node left --> we have to break - break; - } - - if(queue->empty() && nodes_left > 0) { - //disconnected graph -> find a new start node among those that havent been touched - NodeID k = random_functions::nextInt(0, nodes_left-1); - NodeID start_node = 0; - forall_nodes(G, node) { - if(!touched[node]) { - if(k == 0) { - start_node = node; - break; - } else { - k--; - } - } - } endfor - - queue->insert(start_node, 0); - touched[start_node] = true; - } else if (queue->empty() && nodes_left == 0) { - break; - } - - NodeID source = queue->deleteMax(); - G.setPartitionIndex(source, 0); - - nodes_left--; - cur_partition_weight += G.getNodeWeight(source); - - if(cur_partition_weight >= (NodeWeight)config.grow_target) break; - - forall_out_edges(G, e, source) { - NodeID target = G.getEdgeTarget(e); - if(G.getPartitionIndex(target) == 1) { //then we might need to update the gain! - Gain gain = compute_gain(G, target, 0); - touched[target] = true; - - if(queue->contains(target)) { - //change the gain - queue->changeKey(target, gain); - } else { - //insert - queue->insert(target, gain); - } - - } - } endfor + if(G.number_of_nodes() == 0) return; + + //NodeID startNode = random_functions::nextInt(0, G.number_of_nodes()-1); + NodeID startNode = find_start_node(config, G); + + std::vector touched(G.number_of_nodes(), false); + touched[startNode] = true; + NodeWeight cur_partition_weight = 0; + + forall_nodes(G, node) { + G.setPartitionIndex(node, 1); + } endfor + + // The queued start vertex has not been assigned yet; see grow_regions_bfs. + NodeID nodes_left = G.number_of_nodes(); + + //now perform a pseudo dijkstra to get a partition + maxNodeHeap* queue = new maxNodeHeap(); + queue->insert(startNode, 0); // in this case the gain doesn't really matter + + for(;;) { + if( nodes_left == 1 ) { + //only one node left --> we have to break + break; + } + + if(queue->empty() && nodes_left > 0) { + //disconnected graph -> find a new start node among those that havent been touched + NodeID k = random_functions::nextInt(0, nodes_left-1); + NodeID start_node = 0; + forall_nodes(G, node) { + if(!touched[node]) { + if(k == 0) { + start_node = node; + break; + } else { + k--; + } + } + } endfor + + queue->insert(start_node, 0); + touched[start_node] = true; + } else if (queue->empty() && nodes_left == 0) { + break; + } + + NodeID source = queue->deleteMax(); + G.setPartitionIndex(source, 0); + + nodes_left--; + cur_partition_weight += G.getNodeWeight(source); + + if(cur_partition_weight >= (NodeWeight)config.grow_target) break; + + forall_out_edges(G, e, source) { + NodeID target = G.getEdgeTarget(e); + if(G.getPartitionIndex(target) == 1) { //then we might need to update the gain! + Gain gain = compute_gain(G, target, 0); + touched[target] = true; + + if(queue->contains(target)) { + //change the gain + queue->changeKey(target, gain); + } else { + //insert + queue->insert(target, gain); } - delete queue; + + } + } endfor +} + delete queue; +} } diff --git a/parallel/modified_kahip/lib/partition/initial_partitioning/bipartition.h b/parallel/modified_kahip/lib/partition/initial_partitioning/bipartition.h index 7200ee35..8aacd3bf 100644 --- a/parallel/modified_kahip/lib/partition/initial_partitioning/bipartition.h +++ b/parallel/modified_kahip/lib/partition/initial_partitioning/bipartition.h @@ -9,32 +9,34 @@ #define BIPARTITION_7I4IR31Y #include "initial_partitioner.h" - +namespace kahip::modified { class bipartition : public initial_partitioner { - public: - bipartition(); - virtual ~bipartition(); - - void initial_partition( const PartitionConfig & config, - const unsigned int seed, - graph_access & G, - int* partition_map); - - void initial_partition( const PartitionConfig & config, - const unsigned int seed, - graph_access & G, - int* xadj, - int* adjncy, - int* vwgt, - int* adjwgt, - int* partition_map); - - private: - void grow_regions_bfs(const PartitionConfig & config, graph_access & G); - void grow_regions_fm(const PartitionConfig & config, graph_access & G); - NodeID find_start_node( const PartitionConfig & config, graph_access & G); - void post_fm(const PartitionConfig & config, graph_access & G); - inline Gain compute_gain( graph_access & G, NodeID node, PartitionID targeting_partition); +friend struct bipartition_invariant_test_access; + +public: + bipartition(); + virtual ~bipartition(); + + void initial_partition( const PartitionConfig & config, + const unsigned int seed, + graph_access & G, + int* partition_map); + + void initial_partition( const PartitionConfig & config, + const unsigned int seed, + graph_access & G, + int* xadj, + int* adjncy, + int* vwgt, + int* adjwgt, + int* partition_map); + +private: + void grow_regions_bfs(const PartitionConfig & config, graph_access & G); + void grow_regions_fm(const PartitionConfig & config, graph_access & G); + NodeID find_start_node( const PartitionConfig & config, graph_access & G); + void post_fm(const PartitionConfig & config, graph_access & G); + inline Gain compute_gain( graph_access & G, NodeID node, PartitionID targeting_partition); }; @@ -52,6 +54,6 @@ inline Gain bipartition::compute_gain( graph_access & G, NodeID node, PartitionI return gain; } - +} #endif /* end of include guard: BIPARTITION_7I4IR31Y */ diff --git a/parallel/modified_kahip/lib/partition/initial_partitioning/initial_partition_bipartition.cpp b/parallel/modified_kahip/lib/partition/initial_partitioning/initial_partition_bipartition.cpp index 73c3e7cb..98abccde 100644 --- a/parallel/modified_kahip/lib/partition/initial_partitioning/initial_partition_bipartition.cpp +++ b/parallel/modified_kahip/lib/partition/initial_partitioning/initial_partition_bipartition.cpp @@ -10,7 +10,7 @@ #include "uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement.h" #include "uncoarsening/refinement/mixed_refinement.h" #include "graph_partitioner.h" - +namespace kahip::modified { initial_partition_bipartition::initial_partition_bipartition() { } @@ -22,52 +22,52 @@ initial_partition_bipartition::~initial_partition_bipartition() { void initial_partition_bipartition::initial_partition( const PartitionConfig & config, const unsigned int seed, graph_access & G, int* partition_map) { - graph_partitioner gp; - PartitionConfig rec_config = config; - rec_config.initial_partitioning_type = INITIAL_PARTITIONING_BIPARTITION; - rec_config.initial_partitioning_repetitions = 0; - rec_config.global_cycle_iterations = 1; - rec_config.use_wcycles = false; - rec_config.use_fullmultigrid = false; - rec_config.fm_search_limit = config.bipartition_post_ml_limits; - rec_config.matching_type = MATCHING_GPA; - rec_config.permutation_quality = PERMUTATION_QUALITY_GOOD; - rec_config.initial_partitioning = true; - rec_config.graph_allready_partitioned = false; - rec_config.label_propagation_refinement = false; - - if( config.cluster_coarsening_during_ip == true) { - rec_config.matching_type = CLUSTER_COARSENING; - rec_config.cluster_coarsening_factor = 12; - rec_config.ensemble_clusterings = false; - } - - - std::streambuf* backup = std::cout.rdbuf(); - std::ofstream ofs; - ofs.open("/dev/null"); - std::cout.rdbuf(ofs.rdbuf()); - - gp.perform_recursive_partitioning(rec_config, G); - - ofs.close(); - std::cout.rdbuf(backup); + graph_partitioner gp; + PartitionConfig rec_config = config; + rec_config.initial_partitioning_type = INITIAL_PARTITIONING_BIPARTITION; + rec_config.initial_partitioning_repetitions = 0; + rec_config.global_cycle_iterations = 1; + rec_config.use_wcycles = false; + rec_config.use_fullmultigrid = false; + rec_config.fm_search_limit = config.bipartition_post_ml_limits; + rec_config.matching_type = MATCHING_GPA; + rec_config.permutation_quality = PERMUTATION_QUALITY_GOOD; + rec_config.initial_partitioning = true; + rec_config.graph_allready_partitioned = false; + rec_config.label_propagation_refinement = false; + + if( config.cluster_coarsening_during_ip == true) { + rec_config.matching_type = CLUSTER_COARSENING; + rec_config.cluster_coarsening_factor = 12; + rec_config.ensemble_clusterings = false; + } + + + std::streambuf* backup = std::cout.rdbuf(); + std::ofstream ofs; + ofs.open("/dev/null"); + std::cout.rdbuf(ofs.rdbuf()); + + gp.perform_recursive_partitioning(rec_config, G); + + ofs.close(); + std::cout.rdbuf(backup); + + forall_nodes(G, n) { + partition_map[n] = G.getPartitionIndex(n); + } endfor - forall_nodes(G, n) { - partition_map[n] = G.getPartitionIndex(n); - } endfor - -} +} -void initial_partition_bipartition::initial_partition( const PartitionConfig & config, - const unsigned int seed, - graph_access & G, +void initial_partition_bipartition::initial_partition( const PartitionConfig & config, + const unsigned int seed, + graph_access & G, int* xadj, - int* adjncy, - int* vwgt, + int* adjncy, + int* vwgt, int* adjwgt, int* partition_map) { - std::cout << "not implemented yet" << std::endl; + std::cout << "not implemented yet" << std::endl; } - +} \ No newline at end of file diff --git a/parallel/modified_kahip/lib/partition/initial_partitioning/initial_partition_bipartition.h b/parallel/modified_kahip/lib/partition/initial_partitioning/initial_partition_bipartition.h index ac652079..2989eb8b 100644 --- a/parallel/modified_kahip/lib/partition/initial_partitioning/initial_partition_bipartition.h +++ b/parallel/modified_kahip/lib/partition/initial_partitioning/initial_partition_bipartition.h @@ -9,23 +9,23 @@ #define INITIAL_PARTITION_BIPARTITION_HMA7329W #include "initial_partitioner.h" - +namespace kahip::modified { class initial_partition_bipartition : public initial_partitioner { public: - initial_partition_bipartition(); - virtual ~initial_partition_bipartition(); + initial_partition_bipartition(); + virtual ~initial_partition_bipartition(); - void initial_partition( const PartitionConfig & config, const unsigned int seed, graph_access & G, int* partition_map); + void initial_partition( const PartitionConfig & config, const unsigned int seed, graph_access & G, int* partition_map); - void initial_partition( const PartitionConfig & config, const unsigned int seed, - graph_access & G, - int* xadj, - int* adjncy, - int* vwgt, - int* adjwgt, - int* partition_map); + void initial_partition( const PartitionConfig & config, const unsigned int seed, + graph_access & G, + int* xadj, + int* adjncy, + int* vwgt, + int* adjwgt, + int* partition_map); }; - +} #endif /* end of include guard: INITIAL_PARTITION_BIPARTITION_HMA7329W */ diff --git a/parallel/modified_kahip/lib/partition/initial_partitioning/initial_partitioner.cpp b/parallel/modified_kahip/lib/partition/initial_partitioning/initial_partitioner.cpp index 63669ec9..ad861e88 100644 --- a/parallel/modified_kahip/lib/partition/initial_partitioning/initial_partitioner.cpp +++ b/parallel/modified_kahip/lib/partition/initial_partitioning/initial_partitioner.cpp @@ -6,7 +6,7 @@ *****************************************************************************/ #include "initial_partitioner.h" - +namespace kahip::modified { initial_partitioner::initial_partitioner() { } @@ -14,4 +14,5 @@ initial_partitioner::initial_partitioner() { initial_partitioner::~initial_partitioner() { } +} diff --git a/parallel/modified_kahip/lib/partition/initial_partitioning/initial_partitioner.h b/parallel/modified_kahip/lib/partition/initial_partitioning/initial_partitioner.h index f3a176f3..4e71e814 100644 --- a/parallel/modified_kahip/lib/partition/initial_partitioning/initial_partitioner.h +++ b/parallel/modified_kahip/lib/partition/initial_partitioning/initial_partitioner.h @@ -10,25 +10,25 @@ #include "partition_config.h" #include "data_structure/graph_access.h" - +namespace kahip::modified { class initial_partitioner { - public: - initial_partitioner( ); - virtual ~initial_partitioner(); +public: + initial_partitioner( ); + virtual ~initial_partitioner(); - virtual void initial_partition( const PartitionConfig & config, const unsigned int seed, - graph_access & G, - int* xadj, - int* adjncy, - int* vwgt, - int* adjwgt, - int* partition_map) = 0; + virtual void initial_partition( const PartitionConfig & config, const unsigned int seed, + graph_access & G, + int* xadj, + int* adjncy, + int* vwgt, + int* adjwgt, + int* partition_map) = 0; - virtual void initial_partition(const PartitionConfig & config, - const unsigned int seed, - graph_access & G, - int* partition_map) = 0; + virtual void initial_partition(const PartitionConfig & config, + const unsigned int seed, + graph_access & G, + int* partition_map) = 0; }; - +} #endif /* end of include guard: INITIAL_PARTITIONER_TJKC6RWY */ diff --git a/parallel/modified_kahip/lib/partition/initial_partitioning/initial_partitioning.cpp b/parallel/modified_kahip/lib/partition/initial_partitioning/initial_partitioning.cpp index 9fe7623b..b162ecc5 100644 --- a/parallel/modified_kahip/lib/partition/initial_partitioning/initial_partitioning.cpp +++ b/parallel/modified_kahip/lib/partition/initial_partitioning/initial_partitioning.cpp @@ -7,6 +7,8 @@ #include +#include + #include "bipartition.h" #include "graph_partition_assertions.h" #include "graph_partitioner.h" @@ -16,7 +18,7 @@ #include "quality_metrics.h" #include "random_functions.h" #include "timer.h" - +namespace kahip::modified { initial_partitioning::initial_partitioning() { } @@ -26,99 +28,99 @@ initial_partitioning::~initial_partitioning() { } void initial_partitioning::perform_initial_partitioning(const PartitionConfig & config, graph_hierarchy & hierarchy) { - graph_access& G = *hierarchy.get_coarsest(); - perform_initial_partitioning(config, G); + graph_access& G = *hierarchy.get_coarsest(); + perform_initial_partitioning(config, G); } void initial_partitioning::perform_initial_partitioning(const PartitionConfig & config, graph_access & G) { - initial_partitioner* partition = NULL; - switch(config.initial_partitioning_type) { - case INITIAL_PARTITIONING_RECPARTITION: - partition = new initial_partition_bipartition(); - break; - case INITIAL_PARTITIONING_BIPARTITION: - partition = new bipartition(); - break; - - - } - - quality_metrics qm; - EdgeWeight best_cut; - int* best_map = new int[G.number_of_nodes()]; - if(config.graph_allready_partitioned && !config.omit_given_partitioning) { - best_cut = qm.edge_cut(G); - forall_nodes(G, n) { - best_map[n] = G.getPartitionIndex(n); - } endfor - } else { - best_cut = std::numeric_limits::max(); - } - - timer t; - t.restart(); - int* partition_map = new int[G.number_of_nodes()]; - unsigned reps_to_do = (unsigned) std::max((int)ceil(config.initial_partitioning_repetitions/(double)log2(config.k)),2); - - if(config.initial_partitioning_repetitions == 0) { - reps_to_do = 1; - } - if(config.eco) { - //bound the number of initial partitioning repetions - reps_to_do = std::min((int)config.minipreps, (int)reps_to_do); - } - - PRINT(std::cout << "no of initial partitioning repetitions = " << reps_to_do << std::endl;); - PRINT(std::cout << "no of nodes for partition = " << G.number_of_nodes() << std::endl;); - if(!((config.graph_allready_partitioned && config.no_new_initial_partitioning) || config.omit_given_partitioning)) { - for(unsigned int rep = 0; rep < reps_to_do; rep++) { - unsigned seed = random_functions::nextInt(0, std::numeric_limits::max()); - PartitionConfig working_config = config; - working_config.combine = false; - partition->initial_partition(working_config, seed, G, partition_map); - - EdgeWeight cur_cut = qm.edge_cut(G, partition_map); - if(cur_cut < best_cut) { - PRINT(std::cout << "log>" << "improved the current initial partitiong from " << best_cut - << " to " << cur_cut << std::endl;) - - forall_nodes(G, n) { - best_map[n] = partition_map[n]; - } endfor - - best_cut = cur_cut; - if(best_cut == 0) break; - } - } - - forall_nodes(G, n) { - G.setPartitionIndex(n,best_map[n]); - } endfor - } - - G.set_partition_count(config.k); - - PRINT(std::cout << "initial partitioning took " << t.elapsed() << std::endl;) - PRINT(std::cout << "log>" << "current initial balance " << qm.balance(G) << std::endl;) - - if(config.initial_partition_optimize || config.combine) { - initial_refinement iniref; - iniref.optimize(config, G, best_cut); - } - - PRINT(std::cout << "log>" << "final current initial partitiong from " << best_cut - << " to " << best_cut << std::endl;) - - if(!(config.graph_allready_partitioned && config.no_new_initial_partitioning)) { - PRINT(std::cout << "finalinitialcut " << best_cut << std::endl;) - PRINT(std::cout << "log>" << "final current initial balance " << qm.balance(G) << std::endl;) - } - - ASSERT_TRUE(graph_partition_assertions::assert_graph_has_kway_partition(config, G)); - - delete[] partition_map; - delete[] best_map; - delete partition; + initial_partitioner* partition = NULL; + switch(config.initial_partitioning_type) { + case INITIAL_PARTITIONING_RECPARTITION: + partition = new initial_partition_bipartition(); + break; + case INITIAL_PARTITIONING_BIPARTITION: + partition = new bipartition(); + break; + default: + throw std::invalid_argument("unknown initial partitioning strategy"); + } + + quality_metrics qm; + EdgeWeight best_cut; + int* best_map = new int[G.number_of_nodes()]; + if(config.graph_allready_partitioned && !config.omit_given_partitioning) { + best_cut = qm.edge_cut(G); + forall_nodes(G, n) { + best_map[n] = G.getPartitionIndex(n); + } endfor +} else { + best_cut = std::numeric_limits::max(); +} + + timer t; + t.restart(); + int* partition_map = new int[G.number_of_nodes()]; + unsigned reps_to_do = (unsigned) std::max((int)ceil(config.initial_partitioning_repetitions/(double)log2(config.k)),2); + + if(config.initial_partitioning_repetitions == 0) { + reps_to_do = 1; + } + if(config.eco) { + //bound the number of initial partitioning repetions + reps_to_do = std::min((int)config.minipreps, (int)reps_to_do); + } + + PRINT(std::cout << "no of initial partitioning repetitions = " << reps_to_do << std::endl;); + PRINT(std::cout << "no of nodes for partition = " << G.number_of_nodes() << std::endl;); + if(!((config.graph_allready_partitioned && config.no_new_initial_partitioning) || config.omit_given_partitioning)) { + for(unsigned int rep = 0; rep < reps_to_do; rep++) { + unsigned seed = random_functions::nextInt(0, std::numeric_limits::max()); + PartitionConfig working_config = config; + working_config.combine = false; + partition->initial_partition(working_config, seed, G, partition_map); + + EdgeWeight cur_cut = qm.edge_cut(G, partition_map); + if(cur_cut < best_cut) { + PRINT(std::cout << "log>" << "improved the current initial partitiong from " << best_cut + << " to " << cur_cut << std::endl;) + + forall_nodes(G, n) { + best_map[n] = partition_map[n]; + } endfor + + best_cut = cur_cut; + if(best_cut == 0) break; + } + } + + forall_nodes(G, n) { + G.setPartitionIndex(n,best_map[n]); + } endfor } + G.set_partition_count(config.k); + + PRINT(std::cout << "initial partitioning took " << t.elapsed() << std::endl;) + PRINT(std::cout << "log>" << "current initial balance " << qm.balance(G) << std::endl;) + + if(config.initial_partition_optimize || config.combine) { + initial_refinement iniref; + iniref.optimize(config, G, best_cut); + } + + PRINT(std::cout << "log>" << "final current initial partitiong from " << best_cut + << " to " << best_cut << std::endl;) + + if(!(config.graph_allready_partitioned && config.no_new_initial_partitioning)) { + PRINT(std::cout << "finalinitialcut " << best_cut << std::endl;) + PRINT(std::cout << "log>" << "final current initial balance " << qm.balance(G) << std::endl;) +} + + ASSERT_TRUE(graph_partition_assertions::assert_graph_has_kway_partition(config, G)); + + delete[] partition_map; + delete[] best_map; + delete partition; +} +} diff --git a/parallel/modified_kahip/lib/partition/initial_partitioning/initial_partitioning.h b/parallel/modified_kahip/lib/partition/initial_partitioning/initial_partitioning.h index 892bf8af..62c51bb0 100644 --- a/parallel/modified_kahip/lib/partition/initial_partitioning/initial_partitioning.h +++ b/parallel/modified_kahip/lib/partition/initial_partitioning/initial_partitioning.h @@ -10,14 +10,14 @@ #include "data_structure/graph_hierarchy.h" #include "partition_config.h" - +namespace kahip::modified { class initial_partitioning { public: - initial_partitioning( ); - virtual ~initial_partitioning(); - void perform_initial_partitioning(const PartitionConfig & config, graph_hierarchy & hierarchy); - void perform_initial_partitioning(const PartitionConfig & config, graph_access & G); + initial_partitioning( ); + virtual ~initial_partitioning(); + void perform_initial_partitioning(const PartitionConfig & config, graph_hierarchy & hierarchy); + void perform_initial_partitioning(const PartitionConfig & config, graph_access & G); }; - +} #endif /* end of include guard: INITIAL_PARTITIONING_D7VA0XO9 */ diff --git a/parallel/modified_kahip/lib/partition/initial_partitioning/initial_refinement/initial_refinement.cpp b/parallel/modified_kahip/lib/partition/initial_partitioning/initial_refinement/initial_refinement.cpp index 18ef8f5c..39ecfc33 100644 --- a/parallel/modified_kahip/lib/partition/initial_partitioning/initial_refinement/initial_refinement.cpp +++ b/parallel/modified_kahip/lib/partition/initial_partitioning/initial_refinement/initial_refinement.cpp @@ -8,7 +8,7 @@ #include "initial_refinement.h" #include "coarsening/coarsening.h" #include "uncoarsening/uncoarsening.h" - +namespace kahip::modified { initial_refinement::initial_refinement() { } @@ -19,27 +19,28 @@ initial_refinement::~initial_refinement() { int initial_refinement::optimize( const PartitionConfig & config, graph_access & G, EdgeWeight & initial_cut) { - PartitionConfig partition_config = config; - partition_config.graph_allready_partitioned = true; - partition_config.stop_rule = STOP_RULE_STRONG; - partition_config.fm_search_limit = partition_config.initial_partition_optimize_fm_limits; - partition_config.kway_fm_search_limit = partition_config.initial_partition_optimize_fm_limits; - partition_config.local_multitry_fm_alpha = partition_config.initial_partition_optimize_multitry_fm_alpha; - partition_config.local_multitry_rounds = partition_config.initial_partition_optimize_multitry_rounds; - partition_config.matching_type = MATCHING_GPA; - partition_config.gpa_grow_paths_between_blocks = false; - partition_config.kaffpa_perfectly_balanced_refinement = false; // for runtime reasons - - graph_hierarchy hierarchy; - - coarsening coarsen; - coarsen.perform_coarsening(partition_config, G, hierarchy); - - //ommit initial partitioning since we have the partition allread given - uncoarsening uncoarsen; - int improvement = 0; - improvement = uncoarsen.perform_uncoarsening(partition_config, hierarchy); - initial_cut -= improvement; - - return improvement; -} + PartitionConfig partition_config = config; + partition_config.graph_allready_partitioned = true; + partition_config.stop_rule = STOP_RULE_STRONG; + partition_config.fm_search_limit = partition_config.initial_partition_optimize_fm_limits; + partition_config.kway_fm_search_limit = partition_config.initial_partition_optimize_fm_limits; + partition_config.local_multitry_fm_alpha = partition_config.initial_partition_optimize_multitry_fm_alpha; + partition_config.local_multitry_rounds = partition_config.initial_partition_optimize_multitry_rounds; + partition_config.matching_type = MATCHING_GPA; + partition_config.gpa_grow_paths_between_blocks = false; + partition_config.kaffpa_perfectly_balanced_refinement = false; // for runtime reasons + + graph_hierarchy hierarchy; + + coarsening coarsen; + coarsen.perform_coarsening(partition_config, G, hierarchy); + + //ommit initial partitioning since we have the partition allread given + uncoarsening uncoarsen; + int improvement = 0; + improvement = uncoarsen.perform_uncoarsening(partition_config, hierarchy); + initial_cut -= improvement; + + return improvement; +} +} \ No newline at end of file diff --git a/parallel/modified_kahip/lib/partition/initial_partitioning/initial_refinement/initial_refinement.h b/parallel/modified_kahip/lib/partition/initial_partitioning/initial_refinement/initial_refinement.h index 2f0e27e6..cccd0bd8 100644 --- a/parallel/modified_kahip/lib/partition/initial_partitioning/initial_refinement/initial_refinement.h +++ b/parallel/modified_kahip/lib/partition/initial_partitioning/initial_refinement/initial_refinement.h @@ -10,16 +10,16 @@ #include "data_structure/graph_access.h" #include "partition_config.h" - +namespace kahip::modified { class initial_refinement { public: - initial_refinement( ); - virtual ~initial_refinement(); + initial_refinement( ); + virtual ~initial_refinement(); - int optimize( const PartitionConfig & config, - graph_access & G, - EdgeWeight & initial_cut); + int optimize( const PartitionConfig & config, + graph_access & G, + EdgeWeight & initial_cut); }; - +} #endif /* end of include guard: INITIAL_REFINEMENT_LDIIF5CG */ diff --git a/parallel/modified_kahip/lib/partition/partition_config.h b/parallel/modified_kahip/lib/partition/partition_config.h index aed9294f..cac89afa 100644 --- a/parallel/modified_kahip/lib/partition/partition_config.h +++ b/parallel/modified_kahip/lib/partition/partition_config.h @@ -9,321 +9,321 @@ #define PARTITION_CONFIG_DI1ES4T0 #include "definitions.h" - +namespace kahip::modified { // Configuration for the partitioning. struct PartitionConfig { - PartitionConfig() {} + PartitionConfig() {} + + //============================================================ + //=======================MATCHING============================= + //============================================================ + bool edge_rating_tiebreaking; + + EdgeRating edge_rating; + + PermutationQuality permutation_quality; + + MatchingType matching_type; + + bool match_islands; + + bool first_level_random_matching; + + bool rate_first_level_inner_outer; + + NodeWeight max_vertex_weight; - //============================================================ - //=======================MATCHING============================= - //============================================================ - bool edge_rating_tiebreaking; + NodeWeight largest_graph_weight; - EdgeRating edge_rating; - - PermutationQuality permutation_quality; + unsigned aggressive_random_levels; - MatchingType matching_type; - - bool match_islands; + bool disable_max_vertex_weight_constraint; - bool first_level_random_matching; - - bool rate_first_level_inner_outer; + //============================================================ + //===================INITIAL PARTITIONING===================== + //============================================================ + unsigned int initial_partitioning_repetitions; - NodeWeight max_vertex_weight; - - NodeWeight largest_graph_weight; + unsigned int minipreps; - unsigned aggressive_random_levels; - - bool disable_max_vertex_weight_constraint; + bool refined_bubbling; - //============================================================ - //===================INITIAL PARTITIONING===================== - //============================================================ - unsigned int initial_partitioning_repetitions; + InitialPartitioningType initial_partitioning_type; - unsigned int minipreps; + bool initial_partition_optimize; - bool refined_bubbling; + BipartitionAlgorithm bipartition_algorithm; - InitialPartitioningType initial_partitioning_type; + bool initial_partitioning; - bool initial_partition_optimize; + int bipartition_tries; - BipartitionAlgorithm bipartition_algorithm; + int bipartition_post_fm_limits; - bool initial_partitioning; + int bipartition_post_ml_limits; - int bipartition_tries; + //============================================================ + //====================REFINEMENT PARAMETERS=================== + //============================================================ + bool corner_refinement_enabled; - int bipartition_post_fm_limits; + bool use_bucket_queues; - int bipartition_post_ml_limits; + RefinementType refinement_type; - //============================================================ - //====================REFINEMENT PARAMETERS=================== - //============================================================ - bool corner_refinement_enabled; + PermutationQuality permutation_during_refinement; - bool use_bucket_queues; + ImbalanceType imbalance; - RefinementType refinement_type; + unsigned bubbling_iterations; - PermutationQuality permutation_during_refinement; + unsigned kway_rounds; - ImbalanceType imbalance; + bool quotient_graph_refinement_disabled; - unsigned bubbling_iterations; - - unsigned kway_rounds; - - bool quotient_graph_refinement_disabled; + KWayStopRule kway_stop_rule; - KWayStopRule kway_stop_rule; + double kway_adaptive_limits_alpha; - double kway_adaptive_limits_alpha; + double kway_adaptive_limits_beta; - double kway_adaptive_limits_beta; + unsigned max_flow_iterations; - unsigned max_flow_iterations; + unsigned local_multitry_rounds; - unsigned local_multitry_rounds; - - unsigned local_multitry_fm_alpha; + unsigned local_multitry_fm_alpha; - bool graph_allready_partitioned; + bool graph_allready_partitioned; - unsigned int fm_search_limit; - - unsigned int kway_fm_search_limit; + unsigned int fm_search_limit; - NodeWeight upper_bound_partition; + unsigned int kway_fm_search_limit; - double bank_account_factor; + NodeWeight upper_bound_partition; - RefinementSchedulingAlgorithm refinement_scheduling_algorithm; + double bank_account_factor; - bool most_balanced_minimum_cuts; - - unsigned toposort_iterations; + RefinementSchedulingAlgorithm refinement_scheduling_algorithm; - bool softrebalance; + bool most_balanced_minimum_cuts; - bool rebalance; + unsigned toposort_iterations; - double flow_region_factor; + bool softrebalance; - bool gpa_grow_paths_between_blocks; + bool rebalance; - //======================================= - //==========GLOBAL SEARCH PARAMETERS===== - //======================================= - unsigned global_cycle_iterations; + double flow_region_factor; - bool use_wcycles; + bool gpa_grow_paths_between_blocks; - bool use_fullmultigrid; + //======================================= + //==========GLOBAL SEARCH PARAMETERS===== + //======================================= + unsigned global_cycle_iterations; - unsigned level_split; + bool use_wcycles; - bool no_new_initial_partitioning; + bool use_fullmultigrid; - bool omit_given_partitioning; + unsigned level_split; - StopRule stop_rule; + bool no_new_initial_partitioning; - int num_vert_stop_factor; - - bool no_change_convergence; + bool omit_given_partitioning; - //======================================= - //===PERFECTLY BALANCED PARTITIONING ==== - //======================================= + StopRule stop_rule; + + int num_vert_stop_factor; + + bool no_change_convergence; + + //======================================= + //===PERFECTLY BALANCED PARTITIONING ==== + //======================================= bool remove_negative_cycles; - bool kaba_include_removal_of_paths; + bool kaba_include_removal_of_paths; - bool kaba_enable_zero_weight_cycles; + bool kaba_enable_zero_weight_cycles; - double kabaE_internal_bal; + double kabaE_internal_bal; - CycleRefinementAlgorithm cycle_refinement_algorithm; + CycleRefinementAlgorithm cycle_refinement_algorithm; - int kaba_internal_no_aug_steps_aug; + int kaba_internal_no_aug_steps_aug; - unsigned kaba_packing_iterations; + unsigned kaba_packing_iterations; - bool kaba_flip_packings; + bool kaba_flip_packings; - MLSRule kaba_lsearch_p; // more localized search pseudo directed + MLSRule kaba_lsearch_p; // more localized search pseudo directed - bool kaffpa_perfectly_balanced_refinement; + bool kaffpa_perfectly_balanced_refinement; - unsigned kaba_unsucc_iterations; + unsigned kaba_unsucc_iterations; - - //======================================= - //============PAR_PSEUDOMH / MH ========= - //======================================= + + //======================================= + //============PAR_PSEUDOMH / MH ========= + //======================================= double time_limit; - double epsilon; + double epsilon; unsigned no_unsuc_reps; unsigned local_partitioning_repetitions; - bool mh_plain_repetitions; - - bool mh_easy_construction; + bool mh_plain_repetitions; + + bool mh_easy_construction; - bool mh_enable_gal_combine; + bool mh_enable_gal_combine; - bool mh_no_mh; + bool mh_no_mh; - bool mh_print_log; + bool mh_print_log; - int mh_flip_coin; + int mh_flip_coin; - int mh_initial_population_fraction; + int mh_initial_population_fraction; - bool mh_disable_cross_combine; + bool mh_disable_cross_combine; - bool mh_cross_combine_original_k; + bool mh_cross_combine_original_k; - bool mh_disable_nc_combine; + bool mh_disable_nc_combine; - bool mh_disable_combine; + bool mh_disable_combine; - bool mh_enable_quickstart; + bool mh_enable_quickstart; - bool mh_disable_diversify_islands; + bool mh_disable_diversify_islands; - bool mh_diversify; + bool mh_diversify; - bool mh_diversify_best; + bool mh_diversify_best; - bool mh_enable_tournament_selection; + bool mh_enable_tournament_selection; - bool mh_optimize_communication_volume; + bool mh_optimize_communication_volume; - unsigned mh_num_ncs_to_compute; + unsigned mh_num_ncs_to_compute; - unsigned mh_pool_size; + unsigned mh_pool_size; - bool combine; // in this case the second index is filled and edges between both partitions are not contracted + bool combine; // in this case the second index is filled and edges between both partitions are not contracted - unsigned initial_partition_optimize_fm_limits; + unsigned initial_partition_optimize_fm_limits; - unsigned initial_partition_optimize_multitry_fm_alpha; + unsigned initial_partition_optimize_multitry_fm_alpha; - unsigned initial_partition_optimize_multitry_rounds; + unsigned initial_partition_optimize_multitry_rounds; - unsigned walshaw_mh_repetitions; + unsigned walshaw_mh_repetitions; - unsigned scaleing_factor; + unsigned scaleing_factor; - bool scale_back; + bool scale_back; bool suppress_partitioner_output; - unsigned maxT; - - unsigned maxIter; - //======================================= - //===============BUFFOON================= - //======================================= - bool disable_hard_rebalance; + unsigned maxT; - bool buffoon; + unsigned maxIter; + //======================================= + //===============BUFFOON================= + //======================================= + bool disable_hard_rebalance; - bool kabapE; - - bool mh_penalty_for_unconnected; - //======================================= - //===============MISC==================== - //======================================= - std::string input_partition; + bool buffoon; - int seed; + bool kabapE; - bool fast; + bool mh_penalty_for_unconnected; + //======================================= + //===============MISC==================== + //======================================= + std::string input_partition; - bool eco; + int seed; - bool strong; + bool fast; - // number of blocks the graph should be partitioned in - PartitionID k; + bool eco; - bool compute_vertex_separator; + bool strong; - bool only_first_level; + // number of blocks the graph should be partitioned in + PartitionID k; - bool use_balance_singletons; + bool compute_vertex_separator; - int amg_iterations; + bool only_first_level; - std::string graph_filename; + bool use_balance_singletons; - bool kaffpa_perfectly_balance; + int amg_iterations; - //======================================= - //===========SNW PARTITIONING============ - //======================================= - NodeOrderingType node_ordering; + std::string graph_filename; - int cluster_coarsening_factor; + bool kaffpa_perfectly_balance; - bool ensemble_clusterings; + //======================================= + //===========SNW PARTITIONING============ + //======================================= + NodeOrderingType node_ordering; - int label_iterations; + int cluster_coarsening_factor; - int label_iterations_refinement; + bool ensemble_clusterings; - int number_of_clusterings; + int label_iterations; - bool label_propagation_refinement; + int label_iterations_refinement; - double balance_factor; + int number_of_clusterings; - bool cluster_coarsening_during_ip; + bool label_propagation_refinement; - bool set_upperbound; + double balance_factor; - int repetitions; - - //======================================= - //=========LABEL PROPAGATION============= - //======================================= - NodeWeight cluster_upperbound; + bool cluster_coarsening_during_ip; - bool ultra_fast_kaffpaE_interfacecall; + bool set_upperbound; - //======================================= - //=========INITIAL PARTITIONING========== - //======================================= + int repetitions; - // variables controling the size of the blocks during - // multilevel recursive bisection - // (for the case where k is not a power of 2) - std::vector target_weights; + //======================================= + //=========LABEL PROPAGATION============= + //======================================= + NodeWeight cluster_upperbound; - bool initial_bipartitioning; + bool ultra_fast_kaffpaE_interfacecall; - int grow_target; + //======================================= + //=========INITIAL PARTITIONING========== + //======================================= - //======================================= - //===============Shared Mem OMP========== - //======================================= - bool enable_omp; + // variables controling the size of the blocks during + // multilevel recursive bisection + // (for the case where k is not a power of 2) + std::vector target_weights; - void LogDump(FILE *out) const { - } -}; + bool initial_bipartitioning; + + int grow_target; + //======================================= + //===============Shared Mem OMP========== + //======================================= + bool enable_omp; + + void LogDump(FILE *out) const { + } +}; +} #endif /* end of include guard: PARTITION_CONFIG_DI1ES4T0 */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/advanced_models.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/advanced_models.cpp index 943a5211..e733dcf0 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/advanced_models.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/advanced_models.cpp @@ -17,7 +17,7 @@ #include "uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_core.h" #include "uncoarsening/refinement/kway_graph_refinement/kway_stop_rule.h" #include "uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/vertex_moved_hashtable.h" - +namespace kahip::modified { unsigned long advanced_models::conflicts = 0; @@ -36,171 +36,171 @@ bool advanced_models::compute_vertex_movements_rebalance_ultra( PartitionConfig unsigned & steps) { - graph_access G_bar; - boundary.getUnderlyingQuotientGraph(G_bar); + graph_access G_bar; + boundary.getUnderlyingQuotientGraph(G_bar); - aqg.prepare(config, G, G_bar, steps); + aqg.prepare(config, G, G_bar, steps); - std::vector feasable_edge; - std::vector id_mapping; - NodeID s; NodeID t; // start vertex - do { - graph_access cycle_problem; - build_rebalance_model( config, G, G_bar, boundary, aqg, - feasable_edge, steps, cycle_problem, s, t, id_mapping); + std::vector feasable_edge; + std::vector id_mapping; + NodeID s; NodeID t; // start vertex + do { + graph_access cycle_problem; + build_rebalance_model( config, G, G_bar, boundary, aqg, + feasable_edge, steps, cycle_problem, s, t, id_mapping); - //******************************************************************** - //solve the problem - //******************************************************************** - cycle_search cs; - std::vector path; - cs.find_shortest_path(cycle_problem, s, t, path); + //******************************************************************** + //solve the problem + //******************************************************************** + cycle_search cs; + std::vector path; + cs.find_shortest_path(cycle_problem, s, t, path); - //detect conflict -- a block should be at most one time in a cycle - bool conflict_detected = handle_ultra_model_conflicts(config, cycle_problem, - boundary, id_mapping, - feasable_edge, path, - s, aqg, true); - if(!conflict_detected) { - perform_augmented_move(config, G, boundary, path, s, t, aqg); - return true; - } - } while( true ); // at some point the model will become feasable! (otherwise the method would not have been entered and the fall back algorithm would have been applied + //detect conflict -- a block should be at most one time in a cycle + bool conflict_detected = handle_ultra_model_conflicts(config, cycle_problem, + boundary, id_mapping, + feasable_edge, path, + s, aqg, true); + if(!conflict_detected) { + perform_augmented_move(config, G, boundary, path, s, t, aqg); + return true; + } + } while( true ); // at some point the model will become feasable! (otherwise the method would not have been entered and the fall back algorithm would have been applied - return false; + return false; } -bool advanced_models::compute_vertex_movements_rebalance( PartitionConfig & config, - graph_access & G, - complete_boundary & boundary, - augmented_Qgraph & aqg, +bool advanced_models::compute_vertex_movements_rebalance( PartitionConfig & config, + graph_access & G, + complete_boundary & boundary, + augmented_Qgraph & aqg, unsigned & steps) { - graph_access cycle_problem; - graph_access G_bar; - boundary.getUnderlyingQuotientGraph(G_bar); - - aqg.prepare(config, G, G_bar, steps); - - //*********************************************************************** - //build the model - //*********************************************************************** - NodeID number_of_nodes = G_bar.number_of_nodes()*steps + 2; - EdgeID number_of_edges = G_bar.number_of_edges()*steps + 2*number_of_nodes; - - cycle_problem.start_construction(number_of_nodes, number_of_edges); - NodeID s = number_of_nodes - 2; - NodeID t = number_of_nodes - 1 ; - - for( unsigned s_idx = 0; s_idx < steps; s_idx++) { - //create a new layer - forall_nodes(G_bar, lhs) { - NodeID cur_node = cycle_problem.new_node(); - forall_out_edges(G_bar, e, lhs) { - EdgeID rhs = G_bar.getEdgeTarget(e); - - //find the right edge in the augmented quotient graph - boundary_pair bp; - bp.k = config.k; - bp.lhs = lhs; - bp.rhs = rhs; - - - unsigned load_difference = s_idx + 1; - if( aqg.exists_vmovements_of_diff(bp, load_difference) ) { - EdgeID e_bar = cycle_problem.new_edge(cur_node, s_idx*config.k+rhs); - cycle_problem.setEdgeWeight(e_bar, -aqg.get_gain_of_vmovements(bp, load_difference)); - } - - } endfor - - //create a backward edge if it can take s_idx+1 vertices - if( boundary.getBlockWeight(lhs) + s_idx < config.upper_bound_partition) { - EdgeID e_bar = cycle_problem.new_edge(cur_node, t); - cycle_problem.setEdgeWeight(e_bar, 0); - } - } endfor - } + graph_access cycle_problem; + graph_access G_bar; + boundary.getUnderlyingQuotientGraph(G_bar); - s = cycle_problem.new_node(); + aqg.prepare(config, G, G_bar, steps); - //now connect s to all vertices of the layer graph with weight zero - for( unsigned s_idx = 0; s_idx < steps; s_idx++) { - forall_nodes(G_bar, node) { - if( boundary.getBlockWeight(node) > config.upper_bound_partition ) { - EdgeID e = cycle_problem.new_edge(s, s_idx*config.k + node); - cycle_problem.setEdgeWeight(e, 0); - } - } endfor - } + //*********************************************************************** + //build the model + //*********************************************************************** + NodeID number_of_nodes = G_bar.number_of_nodes()*steps + 2; + EdgeID number_of_edges = G_bar.number_of_edges()*steps + 2*number_of_nodes; - t = cycle_problem.new_node(); - cycle_problem.finish_construction(); + cycle_problem.start_construction(number_of_nodes, number_of_edges); + NodeID s = number_of_nodes - 2; + NodeID t = number_of_nodes - 1 ; - //************************************************************************************* - //solve shortest path problem in model - //************************************************************************************* - //check wether t is reachable from s by performing a bfs - std::deque* bfsqueue = new std::deque; - std::vector touched(cycle_problem.number_of_nodes(), false); - bfsqueue->push_back(s); - touched[s] = true; + for( unsigned s_idx = 0; s_idx < steps; s_idx++) { + //create a new layer + forall_nodes(G_bar, lhs) { + NodeID cur_node = cycle_problem.new_node(); + forall_out_edges(G_bar, e, lhs) { + EdgeID rhs = G_bar.getEdgeTarget(e); - cycle_search cs; - std::vector path; - cs.find_shortest_path(cycle_problem, s, t, path); + //find the right edge in the augmented quotient graph + boundary_pair bp; + bp.k = config.k; + bp.lhs = lhs; + bp.rhs = rhs; - //perform the found movements - perform_augmented_move(config, G, boundary, path, s, t, aqg); - return true; + unsigned load_difference = s_idx + 1; + if( aqg.exists_vmovements_of_diff(bp, load_difference) ) { + EdgeID e_bar = cycle_problem.new_edge(cur_node, s_idx*config.k+rhs); + cycle_problem.setEdgeWeight(e_bar, -aqg.get_gain_of_vmovements(bp, load_difference)); + } + + } endfor + + //create a backward edge if it can take s_idx+1 vertices + if( boundary.getBlockWeight(lhs) + s_idx < config.upper_bound_partition) { + EdgeID e_bar = cycle_problem.new_edge(cur_node, t); + cycle_problem.setEdgeWeight(e_bar, 0); + } + } endfor } -bool advanced_models::compute_vertex_movements_ultra_model( PartitionConfig & config, - graph_access & G, - complete_boundary & boundary, - augmented_Qgraph & aqg, - unsigned & steps, bool zero_weight_cycle) { - graph_access G_bar; - boundary.getUnderlyingQuotientGraph(G_bar); + s = cycle_problem.new_node(); - if(!zero_weight_cycle) { - aqg.prepare(config, G, G_bar, steps); - } + //now connect s to all vertices of the layer graph with weight zero + for( unsigned s_idx = 0; s_idx < steps; s_idx++) { + forall_nodes(G_bar, node) { + if( boundary.getBlockWeight(node) > config.upper_bound_partition ) { + EdgeID e = cycle_problem.new_edge(s, s_idx*config.k + node); + cycle_problem.setEdgeWeight(e, 0); + } + } endfor +} - bool found_some; - std::vector feasable_edge; - std::vector id_mapping; - NodeID s; // start vertex - do { - graph_access cycle_problem; - build_ultra_model( config, G, G_bar, boundary, aqg, feasable_edge, steps, cycle_problem, s, id_mapping); - - //******************************************************************** - //solve the problem - //******************************************************************** - cycle_search cs; std::vector cycle; - if( zero_weight_cycle ) { - found_some = cs.find_zero_weight_cycle(cycle_problem, s, cycle); - } else { - found_some = cs.find_negative_cycle(cycle_problem, s, cycle); - } - - if(found_some) { - //detect conflict -- a block should be at most one time in a cycle - bool conflict_detected = handle_ultra_model_conflicts(config, cycle_problem, - boundary, id_mapping, - feasable_edge, cycle, s, aqg); - if(!conflict_detected) { - perform_augmented_move(config, G, boundary, cycle, s, s, aqg); - return true; - } - } - } while( found_some ); - - return false; + t = cycle_problem.new_node(); + cycle_problem.finish_construction(); + + //************************************************************************************* + //solve shortest path problem in model + //************************************************************************************* + //check wether t is reachable from s by performing a bfs + std::deque* bfsqueue = new std::deque; + std::vector touched(cycle_problem.number_of_nodes(), false); + bfsqueue->push_back(s); + touched[s] = true; + + cycle_search cs; + std::vector path; + cs.find_shortest_path(cycle_problem, s, t, path); + + //perform the found movements + perform_augmented_move(config, G, boundary, path, s, t, aqg); + + return true; } +bool advanced_models::compute_vertex_movements_ultra_model( PartitionConfig & config, + graph_access & G, + complete_boundary & boundary, + augmented_Qgraph & aqg, + unsigned & steps, bool zero_weight_cycle) { + graph_access G_bar; + boundary.getUnderlyingQuotientGraph(G_bar); + + if(!zero_weight_cycle) { + aqg.prepare(config, G, G_bar, steps); + } + + bool found_some; + std::vector feasable_edge; + std::vector id_mapping; + NodeID s; // start vertex + do { + graph_access cycle_problem; + build_ultra_model( config, G, G_bar, boundary, aqg, feasable_edge, steps, cycle_problem, s, id_mapping); + + //******************************************************************** + //solve the problem + //******************************************************************** + cycle_search cs; std::vector cycle; + if( zero_weight_cycle ) { + found_some = cs.find_zero_weight_cycle(cycle_problem, s, cycle); + } else { + found_some = cs.find_negative_cycle(cycle_problem, s, cycle); + } + + if(found_some) { + //detect conflict -- a block should be at most one time in a cycle + bool conflict_detected = handle_ultra_model_conflicts(config, cycle_problem, + boundary, id_mapping, + feasable_edge, cycle, s, aqg); + if(!conflict_detected) { + perform_augmented_move(config, G, boundary, cycle, s, s, aqg); + return true; + } + } + } while( found_some ); + + return false; +} +} diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/advanced_models.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/advanced_models.h index ef00d29b..39485cf8 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/advanced_models.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/advanced_models.h @@ -16,76 +16,76 @@ #include "uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_commons.h" #include "uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/two_way_fm.h" #include "uncoarsening/refinement/refinement.h" - +namespace kahip::modified { class advanced_models { - public: - advanced_models(); - virtual ~advanced_models(); - - bool compute_vertex_movements_rebalance( PartitionConfig & config, - graph_access & G, - complete_boundary & boundary, - augmented_Qgraph & aqg, - unsigned & s); - - bool compute_vertex_movements_rebalance_ultra( PartitionConfig & config, - graph_access & G, - complete_boundary & boundary, - augmented_Qgraph & aqg, - unsigned & s); - - bool compute_vertex_movements_ultra_model( PartitionConfig & config, - graph_access & G, - complete_boundary & boundary, - augmented_Qgraph & aqg, - unsigned & s, bool zero_weight_cycle); - - void perform_augmented_move( PartitionConfig & config, - graph_access & G, - complete_boundary & boundary, - std::vector & cycle, - NodeID & s, NodeID & t, - augmented_Qgraph & aqg); - - static unsigned long conflicts; - private: - inline - bool build_ultra_model( PartitionConfig & config, - graph_access & G, - graph_access & G_bar, - complete_boundary & boundary, - augmented_Qgraph & aqg, - std::vector & feasable_edge, - unsigned & steps, - graph_access & cycle_problem, NodeID & s, - std::vector & id_mapping); - - inline - bool build_rebalance_model( PartitionConfig & config, - graph_access & G, - graph_access & G_bar, - complete_boundary & boundary, - augmented_Qgraph & aqg, - std::vector & feasable_edge, - unsigned & steps, - graph_access & cycle_problem, NodeID & s, NodeID & t, - std::vector & id_mapping); - - - inline - bool handle_ultra_model_conflicts( PartitionConfig & config, - graph_access & cycle_problem, - complete_boundary & boundary, - std::vector & id_mapping, - std::vector & feasable_edge, - std::vector< NodeID > & cycle, - NodeID & s, augmented_Qgraph & aqg, bool remove_only_between_layers = false); - - inline - bool cycleorpath_has_conflicts( PartitionConfig & config, - complete_boundary & boundary, - std::vector< NodeID > & cycleorpath, - NodeID & s, augmented_Qgraph & aqg); +public: + advanced_models(); + virtual ~advanced_models(); + + bool compute_vertex_movements_rebalance( PartitionConfig & config, + graph_access & G, + complete_boundary & boundary, + augmented_Qgraph & aqg, + unsigned & s); + + bool compute_vertex_movements_rebalance_ultra( PartitionConfig & config, + graph_access & G, + complete_boundary & boundary, + augmented_Qgraph & aqg, + unsigned & s); + + bool compute_vertex_movements_ultra_model( PartitionConfig & config, + graph_access & G, + complete_boundary & boundary, + augmented_Qgraph & aqg, + unsigned & s, bool zero_weight_cycle); + + void perform_augmented_move( PartitionConfig & config, + graph_access & G, + complete_boundary & boundary, + std::vector & cycle, + NodeID & s, NodeID & t, + augmented_Qgraph & aqg); + + static unsigned long conflicts; +private: + inline + bool build_ultra_model( PartitionConfig & config, + graph_access & G, + graph_access & G_bar, + complete_boundary & boundary, + augmented_Qgraph & aqg, + std::vector & feasable_edge, + unsigned & steps, + graph_access & cycle_problem, NodeID & s, + std::vector & id_mapping); + + inline + bool build_rebalance_model( PartitionConfig & config, + graph_access & G, + graph_access & G_bar, + complete_boundary & boundary, + augmented_Qgraph & aqg, + std::vector & feasable_edge, + unsigned & steps, + graph_access & cycle_problem, NodeID & s, NodeID & t, + std::vector & id_mapping); + + + inline + bool handle_ultra_model_conflicts( PartitionConfig & config, + graph_access & cycle_problem, + complete_boundary & boundary, + std::vector & id_mapping, + std::vector & feasable_edge, + std::vector< NodeID > & cycle, + NodeID & s, augmented_Qgraph & aqg, bool remove_only_between_layers = false); + + inline + bool cycleorpath_has_conflicts( PartitionConfig & config, + complete_boundary & boundary, + std::vector< NodeID > & cycleorpath, + NodeID & s, augmented_Qgraph & aqg); @@ -607,5 +607,5 @@ bool advanced_models::build_ultra_model( PartitionConfig & config, return false; } - +} #endif /* end of include guard: ADVANCED_MODELS_PR6SXN3G */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/augmented_Qgraph.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/augmented_Qgraph.cpp index d03d3308..dd196d07 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/augmented_Qgraph.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/augmented_Qgraph.cpp @@ -6,7 +6,7 @@ *****************************************************************************/ #include "augmented_Qgraph.h" - +namespace kahip::modified { augmented_Qgraph::augmented_Qgraph() : m_max_vertex_weight_difference(0) { } @@ -14,4 +14,4 @@ augmented_Qgraph::augmented_Qgraph() : m_max_vertex_weight_difference(0) { augmented_Qgraph::~augmented_Qgraph() { } - +} diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/augmented_Qgraph.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/augmented_Qgraph.h index ddf0a958..2250cdcb 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/augmented_Qgraph.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/augmented_Qgraph.h @@ -15,7 +15,7 @@ #include "definitions.h" #include "uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_commons.h" #include "uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/two_way_fm.h" - +namespace kahip::modified { struct pairwise_local_search { // a single two way local search std::vector gains; std::vector vertex_movements; @@ -116,11 +116,11 @@ inline bool augmented_Qgraph::exists_vmovements_of_diff( boundary_pair & bp, unsigned & diff) { unsigned internal_idx = diff - 1; if( m_aqg[bp].local_searches.size() > 0 ) { - if(m_aqg[bp].search_to_use.size() > internal_idx) { - if(m_aqg[bp].search_to_use[internal_idx] != -1) { - return true; - } - } + if(m_aqg[bp].search_to_use.size() > internal_idx) { + if(m_aqg[bp].search_to_use[internal_idx] != -1) { + return true; + } + } } return false; @@ -234,5 +234,5 @@ bool augmented_Qgraph::check_conflict( const PartitionConfig & config, return (node == m_aqg[bp].local_searches[local_searches_to_use].vertex_movements[0]); } - +} #endif /* end of include guard: AUGMENTED_QUOTIENT_GRAPH_E5ZEJUBV */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/augmented_Qgraph_fabric.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/augmented_Qgraph_fabric.cpp index 588c3cae..fb8b6d6e 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/augmented_Qgraph_fabric.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/augmented_Qgraph_fabric.cpp @@ -16,7 +16,7 @@ #include "uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_core.h" #include "uncoarsening/refinement/kway_graph_refinement/kway_stop_rule.h" #include "uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/vertex_moved_hashtable.h" - +namespace kahip::modified { augmented_Qgraph_fabric::augmented_Qgraph_fabric() { } @@ -25,535 +25,535 @@ augmented_Qgraph_fabric::~augmented_Qgraph_fabric() { } void augmented_Qgraph_fabric::cleanup_eligible() { - for( unsigned i = 0; i < m_tomake_eligible.size(); i++) { - m_eligible[m_tomake_eligible[i]] = true; - } - m_tomake_eligible.clear(); - } + for( unsigned i = 0; i < m_tomake_eligible.size(); i++) { + m_eligible[m_tomake_eligible[i]] = true; + } + m_tomake_eligible.clear(); +} -bool augmented_Qgraph_fabric::build_augmented_quotient_graph( PartitionConfig & config, - graph_access & G, - complete_boundary & boundary, - augmented_Qgraph & aqg, +bool augmented_Qgraph_fabric::build_augmented_quotient_graph( PartitionConfig & config, + graph_access & G, + complete_boundary & boundary, + augmented_Qgraph & aqg, unsigned & s, bool rebalance, bool plus) { - graph_access G_bar; - boundary.getUnderlyingQuotientGraph(G_bar); - if(m_eligible.size() != G.number_of_nodes()) { - m_eligible.resize(G.number_of_nodes()); - forall_nodes(G, node) { - m_eligible[node] = true; - } endfor - } else { - cleanup_eligible(); - } + graph_access G_bar; + boundary.getUnderlyingQuotientGraph(G_bar); + if(m_eligible.size() != G.number_of_nodes()) { + m_eligible.resize(G.number_of_nodes()); + forall_nodes(G, node) { + m_eligible[node] = true; + } endfor +} else { + cleanup_eligible(); +} - if(!rebalance) { - std::vector vec_bpd; - forall_nodes(G_bar, lhs) { - forall_out_edges(G_bar, e, lhs) { - EdgeID rhs = G_bar.getEdgeTarget(e); - - block_pair_difference bpd; - bpd.lhs = lhs; - bpd.rhs = rhs; - vec_bpd.push_back(bpd); - } endfor - } endfor - - - for( unsigned j = 0; j < config.kaba_packing_iterations; j++) { - random_functions::permutate_vector_good_small(vec_bpd); - bool variant_to_use = plus; - for( unsigned i = 0; i < vec_bpd.size(); i++) { - boundary_pair bp; - bp.k = config.k; - bp.lhs = vec_bpd[i].lhs; - bp.rhs = vec_bpd[i].rhs; - - if( plus && config.kaba_flip_packings) { - //best of both worlds - variant_to_use = random_functions::nextBool(); - } - - local_search( config, variant_to_use, G, boundary, aqg, bp, s); - } - } - } else { - std::vector vec_bpd; - bool graph_model_will_be_feasable = false; - forall_nodes(G_bar, lhs) { - forall_out_edges(G_bar, e, lhs) { - EdgeID rhs = G_bar.getEdgeTarget(e); - - block_pair_difference bpd; - bpd.lhs = lhs; - bpd.rhs = rhs; - vec_bpd.push_back(bpd); - - //make the underlying model feasable - if( boundary.getBlockWeight(lhs) > config.upper_bound_partition - && boundary.getBlockWeight(rhs) < config.upper_bound_partition - && !graph_model_will_be_feasable) { - boundary_pair bp; - bp.k = config.k; - bp.lhs = lhs; - bp.rhs = rhs; - - bool success = local_search( config, false, G, boundary, aqg, bp, s); - - if( success ) { - graph_model_will_be_feasable = true; - } // the else case can happen if the quotient graph data structure is not up to date - } - } endfor - } endfor - - if( !graph_model_will_be_feasable) { - // fall back solution - std::deque* bfsqueue = new std::deque; - std::vector< int > parent(G_bar.number_of_nodes(), -1); - - std::vector start_vertices; - std::vector candidates; - forall_nodes(G_bar, lhs) { - if( boundary.getBlockWeight(lhs) > config.upper_bound_partition ) { - start_vertices.push_back(lhs); - } else if ( boundary.getBlockWeight(lhs) < config.upper_bound_partition) { - candidates.push_back(lhs); - } - } endfor - - random_functions::permutate_vector_good_small(start_vertices); - for( unsigned i = 0; i < start_vertices.size(); i++) { - bfsqueue->push_back(start_vertices[i]); - parent[start_vertices[i]] = start_vertices[i]; - } - - while(!bfsqueue->empty()) { - NodeID lhs = bfsqueue->front(); - bfsqueue->pop_front(); - - forall_out_edges(G_bar, e, lhs) { - NodeID rhs = G_bar.getEdgeTarget(e); - - if(parent[rhs] == -1 && boundary.getDirectedBoundary(lhs, lhs, rhs).size() > 0) { - parent[rhs] = lhs; - bfsqueue->push_back(rhs); - } - } endfor - } - - delete bfsqueue; - - int cur_block; - int start_block; - bool candiate_set_was_empty = false; - std::vector tmp_candidates; - tmp_candidates = candidates; // for the connected component case - do { - if(candidates.size() == 0) { - candiate_set_was_empty = true; - break; - } - unsigned int r_idx = random_functions::nextInt(0, candidates.size()-1); - cur_block = candidates[r_idx]; - std::swap(candidates[r_idx], candidates[candidates.size()-1]); - candidates.pop_back(); - - } while( parent[cur_block] == -1 ); // in this case the vertex is not reachable - - //special case for more connected components, - //move a random node from an overloaded block to cur_block (which is a connected component) - if(candiate_set_was_empty) { - unsigned int r_idx = random_functions::nextInt(0, tmp_candidates.size()-1); - PartitionID cur_block = tmp_candidates[r_idx]; - - do { - unsigned int node = random_functions::nextInt(0, G.number_of_nodes()-1); - PartitionID nodes_block = G.getPartitionIndex(node); - if( nodes_block != cur_block - && boundary.getBlockWeight(nodes_block) > config.upper_bound_partition) { - PartitionID from = G.getPartitionIndex(node); - PartitionID to = cur_block; - perform_simple_move( config, G, boundary, node,from, to); - return true; - } - } while( true ); - } // else - - start_block = cur_block; - std::unordered_map< PartitionID, bool > allready_performed_local_search; - - while( boundary.getBlockWeight( cur_block ) <= config.upper_bound_partition ) { - boundary_pair bp; - bp.k = config.k; - bp.lhs = parent[cur_block]; - bp.rhs = cur_block; - - cur_block = parent[cur_block]; - bool success = local_search( config, false, G, boundary, aqg, bp, 1); - - if(!success) { - candidates.push_back( start_block ); - rebalance_fall_back(config, G, G_bar, boundary, candidates, parent, aqg); // this happens on dense graphs if no node was eligible - return true; - } - allready_performed_local_search[config.k*bp.lhs+bp.rhs] = true; - } - for( unsigned j = 0; j < config.kaba_packing_iterations; j++) { - random_functions::permutate_vector_good_small(vec_bpd); - - for( unsigned i = 0; i < vec_bpd.size(); i++) { - boundary_pair bp; - bp.k = config.k; - bp.lhs = vec_bpd[i].lhs; - bp.rhs = vec_bpd[i].rhs; - - local_search( config, false, G, boundary, aqg, bp, s); - } - } - } else { - for( unsigned j = 0; j < config.kaba_packing_iterations; j++) { - random_functions::permutate_vector_good_small(vec_bpd); - - for( unsigned i = 0; i < vec_bpd.size(); i++) { - boundary_pair bp; - bp.k = config.k; - bp.lhs = vec_bpd[i].lhs; - bp.rhs = vec_bpd[i].rhs; - - local_search( config, false, G, boundary, aqg, bp, s); - } - } - } + if(!rebalance) { + std::vector vec_bpd; + forall_nodes(G_bar, lhs) { + forall_out_edges(G_bar, e, lhs) { + EdgeID rhs = G_bar.getEdgeTarget(e); + + block_pair_difference bpd; + bpd.lhs = lhs; + bpd.rhs = rhs; + vec_bpd.push_back(bpd); + } endfor +} endfor + + +for( unsigned j = 0; j < config.kaba_packing_iterations; j++) { + random_functions::permutate_vector_good_small(vec_bpd); + bool variant_to_use = plus; + for( unsigned i = 0; i < vec_bpd.size(); i++) { + boundary_pair bp; + bp.k = config.k; + bp.lhs = vec_bpd[i].lhs; + bp.rhs = vec_bpd[i].rhs; + + if( plus && config.kaba_flip_packings) { + //best of both worlds + variant_to_use = random_functions::nextBool(); + } + + local_search( config, variant_to_use, G, boundary, aqg, bp, s); + } +} + } else { + std::vector vec_bpd; + bool graph_model_will_be_feasable = false; + forall_nodes(G_bar, lhs) { + forall_out_edges(G_bar, e, lhs) { + EdgeID rhs = G_bar.getEdgeTarget(e); + + block_pair_difference bpd; + bpd.lhs = lhs; + bpd.rhs = rhs; + vec_bpd.push_back(bpd); + + //make the underlying model feasable + if( boundary.getBlockWeight(lhs) > config.upper_bound_partition + && boundary.getBlockWeight(rhs) < config.upper_bound_partition + && !graph_model_will_be_feasable) { + boundary_pair bp; + bp.k = config.k; + bp.lhs = lhs; + bp.rhs = rhs; + + bool success = local_search( config, false, G, boundary, aqg, bp, s); + + if( success ) { + graph_model_will_be_feasable = true; + } // the else case can happen if the quotient graph data structure is not up to date + } + } endfor +} endfor + +if( !graph_model_will_be_feasable) { + // fall back solution + std::deque* bfsqueue = new std::deque; + std::vector< int > parent(G_bar.number_of_nodes(), -1); + + std::vector start_vertices; + std::vector candidates; + forall_nodes(G_bar, lhs) { + if( boundary.getBlockWeight(lhs) > config.upper_bound_partition ) { + start_vertices.push_back(lhs); + } else if ( boundary.getBlockWeight(lhs) < config.upper_bound_partition) { + candidates.push_back(lhs); + } + } endfor + + random_functions::permutate_vector_good_small(start_vertices); + for( unsigned i = 0; i < start_vertices.size(); i++) { + bfsqueue->push_back(start_vertices[i]); + parent[start_vertices[i]] = start_vertices[i]; + } + + while(!bfsqueue->empty()) { + NodeID lhs = bfsqueue->front(); + bfsqueue->pop_front(); + + forall_out_edges(G_bar, e, lhs) { + NodeID rhs = G_bar.getEdgeTarget(e); + + if(parent[rhs] == -1 && boundary.getDirectedBoundary(lhs, lhs, rhs).size() > 0) { + parent[rhs] = lhs; + bfsqueue->push_back(rhs); + } + } endfor +} - } + delete bfsqueue; + + int cur_block; + int start_block; + bool candiate_set_was_empty = false; + std::vector tmp_candidates; + tmp_candidates = candidates; // for the connected component case + do { + if(candidates.size() == 0) { + candiate_set_was_empty = true; + break; + } + unsigned int r_idx = random_functions::nextInt(0, candidates.size()-1); + cur_block = candidates[r_idx]; + std::swap(candidates[r_idx], candidates[candidates.size()-1]); + candidates.pop_back(); + + } while( parent[cur_block] == -1 ); // in this case the vertex is not reachable + + //special case for more connected components, + //move a random node from an overloaded block to cur_block (which is a connected component) + if(candiate_set_was_empty) { + unsigned int r_idx = random_functions::nextInt(0, tmp_candidates.size()-1); + PartitionID cur_block = tmp_candidates[r_idx]; + + do { + unsigned int node = random_functions::nextInt(0, G.number_of_nodes()-1); + PartitionID nodes_block = G.getPartitionIndex(node); + if( nodes_block != cur_block + && boundary.getBlockWeight(nodes_block) > config.upper_bound_partition) { + PartitionID from = G.getPartitionIndex(node); + PartitionID to = cur_block; + perform_simple_move( config, G, boundary, node,from, to); + return true; + } + } while( true ); + } // else + + start_block = cur_block; + std::unordered_map< PartitionID, bool > allready_performed_local_search; + + while( boundary.getBlockWeight( cur_block ) <= config.upper_bound_partition ) { + boundary_pair bp; + bp.k = config.k; + bp.lhs = parent[cur_block]; + bp.rhs = cur_block; + + cur_block = parent[cur_block]; + bool success = local_search( config, false, G, boundary, aqg, bp, 1); + + if(!success) { + candidates.push_back( start_block ); + rebalance_fall_back(config, G, G_bar, boundary, candidates, parent, aqg); // this happens on dense graphs if no node was eligible + return true; + } + allready_performed_local_search[config.k*bp.lhs+bp.rhs] = true; + } + for( unsigned j = 0; j < config.kaba_packing_iterations; j++) { + random_functions::permutate_vector_good_small(vec_bpd); + + for( unsigned i = 0; i < vec_bpd.size(); i++) { + boundary_pair bp; + bp.k = config.k; + bp.lhs = vec_bpd[i].lhs; + bp.rhs = vec_bpd[i].rhs; + + local_search( config, false, G, boundary, aqg, bp, s); + } + } +} else { + for( unsigned j = 0; j < config.kaba_packing_iterations; j++) { + random_functions::permutate_vector_good_small(vec_bpd); + + for( unsigned i = 0; i < vec_bpd.size(); i++) { + boundary_pair bp; + bp.k = config.k; + bp.lhs = vec_bpd[i].lhs; + bp.rhs = vec_bpd[i].rhs; + + local_search( config, false, G, boundary, aqg, bp, s); + } + } +} + + } - return false; + return false; } -bool augmented_Qgraph_fabric::construct_local_searches_on_qgraph_edge( PartitionConfig & config, graph_access & G, - complete_boundary & boundary, augmented_Qgraph & aqg, - boundary_pair & pair, +bool augmented_Qgraph_fabric::construct_local_searches_on_qgraph_edge( PartitionConfig & config, graph_access & G, + complete_boundary & boundary, augmented_Qgraph & aqg, + boundary_pair & pair, unsigned s, bool plus) { - PartitionID lhs = pair.lhs; - PartitionID rhs = pair.rhs; - - //initialize todo list - PartialBoundary & lhs_b = boundary.getDirectedBoundary(lhs, lhs, rhs); - std::vector lhs_boundary; // todo list - forall_boundary_nodes(lhs_b, node) { - if(m_eligible[node]) { - lhs_boundary.push_back(node); - } - } endfor - - if(lhs_boundary.size() == 0) { - //nothing todo - return false; - } - - commons = kway_graph_refinement_commons::getInstance(config); - for( unsigned i = 0; i < 1; i++) { - - if(lhs_boundary.size() == 0) return false; - - pairwise_local_search pls; - - NodeID start_node = lhs_boundary[0]; - find_eligible_start_node( G, lhs, rhs, lhs_boundary, m_eligible, start_node); - - if(!m_eligible[start_node]) return false; // in this case the lhs_boundary was empty and we cant move a node - - if(plus) { - more_locallized_search(config, G, boundary, lhs, rhs, start_node, s, pls); - } else { - directed_more_locallized_search(config, G, boundary, lhs, rhs, start_node, s, pls); - } - - aqg.commit_pairwise_local_search(pair, pls); - - if( plus ) { - // keep things simple - boundary_pair opp_pair = pair; - std::swap(opp_pair.lhs, opp_pair.rhs); - aqg.commit_pairwise_local_search(opp_pair, pls); - } - } - return true; + PartitionID lhs = pair.lhs; + PartitionID rhs = pair.rhs; + + //initialize todo list + PartialBoundary & lhs_b = boundary.getDirectedBoundary(lhs, lhs, rhs); + std::vector lhs_boundary; // todo list + forall_boundary_nodes(lhs_b, node) { + if(m_eligible[node]) { + lhs_boundary.push_back(node); + } + } endfor + + if(lhs_boundary.size() == 0) { + //nothing todo + return false; + } + + commons = kway_graph_refinement_commons::getInstance(config); + for( unsigned i = 0; i < 1; i++) { + + if(lhs_boundary.size() == 0) return false; + + pairwise_local_search pls; + + NodeID start_node = lhs_boundary[0]; + find_eligible_start_node( G, lhs, rhs, lhs_boundary, m_eligible, start_node); + + if(!m_eligible[start_node]) return false; // in this case the lhs_boundary was empty and we cant move a node + + if(plus) { + more_locallized_search(config, G, boundary, lhs, rhs, start_node, s, pls); + } else { + directed_more_locallized_search(config, G, boundary, lhs, rhs, start_node, s, pls); + } + + aqg.commit_pairwise_local_search(pair, pls); + + if( plus ) { + // keep things simple + boundary_pair opp_pair = pair; + std::swap(opp_pair.lhs, opp_pair.rhs); + aqg.commit_pairwise_local_search(opp_pair, pls); + } + } + return true; } //this method performes a directed localized local search and UNDOs them //these searches are for the augmented qgraph structure for balanced graph partitioning -void augmented_Qgraph_fabric::directed_more_locallized_search(PartitionConfig & config, graph_access & G, - complete_boundary & boundary, +void augmented_Qgraph_fabric::directed_more_locallized_search(PartitionConfig & config, graph_access & G, + complete_boundary & boundary, PartitionID & lhs, PartitionID & rhs, NodeID start_node, unsigned & number_of_swaps, pairwise_local_search & pls) { - commons = kway_graph_refinement_commons::getInstance(config); - EdgeWeight max_degree = G.getMaxDegree(); - refinement_pq* queue = new bucket_pq(max_degree); + commons = kway_graph_refinement_commons::getInstance(config); + EdgeWeight max_degree = G.getMaxDegree(); + refinement_pq* queue = new bucket_pq(max_degree); - EdgeWeight int_degree = 0; - EdgeWeight ext_degree = 0; - m_twfm.int_ext_degree(G, start_node, lhs, rhs, int_degree, ext_degree); + EdgeWeight int_degree = 0; + EdgeWeight ext_degree = 0; + m_twfm.int_ext_degree(G, start_node, lhs, rhs, int_degree, ext_degree); - Gain gain = ext_degree - int_degree; - queue->insert(start_node, gain); - - if(queue->empty()) {delete queue; return;} + Gain gain = ext_degree - int_degree; + queue->insert(start_node, gain); - ////roll forwards - int movements = 0; - int overall_gain = 0; + if(queue->empty()) {delete queue; return;} - kway_stop_rule* stopping_rule = new kway_simple_stop_rule(config); + ////roll forwards + int movements = 0; + int overall_gain = 0; - int min_cut_index = 0; - int step_limit = 200; - EdgeWeight input_cut = boundary.getEdgeCut(lhs, rhs); - EdgeWeight min_cut = input_cut; - PartitionID from = lhs; - PartitionID to = rhs; + kway_stop_rule* stopping_rule = new kway_simple_stop_rule(config); - for(movements = 0; movements < (int)number_of_swaps; movements++) { - if( queue->empty() ) { - break; - } - if( stopping_rule->search_should_stop(min_cut_index, movements, step_limit) ) break; + int min_cut_index = 0; + int step_limit = 200; + EdgeWeight input_cut = boundary.getEdgeCut(lhs, rhs); + EdgeWeight min_cut = input_cut; + PartitionID from = lhs; + PartitionID to = rhs; + for(movements = 0; movements < (int)number_of_swaps; movements++) { + if( queue->empty() ) { + break; + } + if( stopping_rule->search_should_stop(min_cut_index, movements, step_limit) ) break; - Gain gain = queue->maxValue(); - NodeID node = queue->deleteMax(); - move_node(config, G, node, queue, boundary, from, to); + Gain gain = queue->maxValue(); + NodeID node = queue->deleteMax(); - overall_gain += gain; - input_cut -= gain; - - stopping_rule->push_statistics(gain); + move_node(config, G, node, queue, boundary, from, to); - if(input_cut < min_cut) { - min_cut_index = movements; - min_cut = input_cut; - } + overall_gain += gain; + input_cut -= gain; - pls.vertex_movements.push_back(node); - pls.block_movements.push_back(to); - pls.gains.push_back(overall_gain); - m_tomake_eligible.push_back(node); - } + stopping_rule->push_statistics(gain); - ////roll backwards - int idx = pls.vertex_movements.size()-1; - for(; idx >= 0; idx--) { - NodeID node = pls.vertex_movements[idx]; - move_node(config, G, node, queue, boundary, to, from); - - //block the neighboring nodes to avoid conflicts - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if(m_eligible[target]) m_tomake_eligible.push_back(target); - m_eligible[target] = false; - } endfor - } - delete queue; - delete stopping_rule; + if(input_cut < min_cut) { + min_cut_index = movements; + min_cut = input_cut; + } + + pls.vertex_movements.push_back(node); + pls.block_movements.push_back(to); + pls.gains.push_back(overall_gain); + m_tomake_eligible.push_back(node); + } + + ////roll backwards + int idx = pls.vertex_movements.size()-1; + for(; idx >= 0; idx--) { + NodeID node = pls.vertex_movements[idx]; + move_node(config, G, node, queue, boundary, to, from); + + //block the neighboring nodes to avoid conflicts + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + if(m_eligible[target]) m_tomake_eligible.push_back(target); + m_eligible[target] = false; + } endfor +} + delete queue; + delete stopping_rule; } //this method performes a directed localized local search and UNDOs them //these searches are for the augmented qgraph structure for balanced graph partitioning -void augmented_Qgraph_fabric::more_locallized_search(PartitionConfig & config, graph_access & G, +void augmented_Qgraph_fabric::more_locallized_search(PartitionConfig & config, graph_access & G, complete_boundary & boundary, PartitionID & lhs, PartitionID & rhs, NodeID start_node, unsigned & number_of_swaps, pairwise_local_search & pls) { - commons = kway_graph_refinement_commons::getInstance(config); - refinement_pq* queue_lhs = NULL; - refinement_pq* queue_rhs = NULL; - EdgeWeight max_degree = G.getMaxDegree(); - queue_lhs = new bucket_pq(max_degree); - queue_rhs = new bucket_pq(max_degree); - - EdgeWeight int_degree = 0; - EdgeWeight ext_degree = 0; - m_twfm.int_ext_degree(G, start_node, lhs, rhs, int_degree, ext_degree); - - Gain gain = ext_degree - int_degree; - queue_lhs->insert(start_node, gain); - - //===================================== - // find a start node for the rhs queue - //===================================== - NodeID start_node_rhs = start_node; // some dummy initialization - Gain max_gain = std::numeric_limits::min(); - forall_out_edges(G, e, start_node) { - NodeID target = G.getEdgeTarget(e); - if( G.getPartitionIndex(target) == rhs && m_eligible[target]) { - m_twfm.int_ext_degree(G, target, rhs, lhs, int_degree, ext_degree); - if( ext_degree - int_degree > max_gain ) { - max_gain = ext_degree - int_degree; - start_node_rhs = target; - } - } - } endfor - //===================================== - - if( m_eligible[start_node_rhs] && start_node_rhs != start_node) { - queue_rhs->insert(start_node_rhs, max_gain); + commons = kway_graph_refinement_commons::getInstance(config); + refinement_pq* queue_lhs = NULL; + refinement_pq* queue_rhs = NULL; + EdgeWeight max_degree = G.getMaxDegree(); + queue_lhs = new bucket_pq(max_degree); + queue_rhs = new bucket_pq(max_degree); + + EdgeWeight int_degree = 0; + EdgeWeight ext_degree = 0; + m_twfm.int_ext_degree(G, start_node, lhs, rhs, int_degree, ext_degree); + + Gain gain = ext_degree - int_degree; + queue_lhs->insert(start_node, gain); + + //===================================== + // find a start node for the rhs queue + //===================================== + NodeID start_node_rhs = start_node; // some dummy initialization + Gain max_gain = std::numeric_limits::min(); + forall_out_edges(G, e, start_node) { + NodeID target = G.getEdgeTarget(e); + if( G.getPartitionIndex(target) == rhs && m_eligible[target]) { + m_twfm.int_ext_degree(G, target, rhs, lhs, int_degree, ext_degree); + if( ext_degree - int_degree > max_gain ) { + max_gain = ext_degree - int_degree; + start_node_rhs = target; + } + } + } endfor + //===================================== + + if( m_eligible[start_node_rhs] && start_node_rhs != start_node) { + queue_rhs->insert(start_node_rhs, max_gain); + } + + if(queue_lhs->empty() || queue_rhs->empty()) {delete queue_lhs; delete queue_rhs; return;} + // queues initalized + + ////roll forwards + int movements = 0; + int overall_gain = 0; + + kway_stop_rule* stopping_rule = new kway_simple_stop_rule(config); + + int min_cut_index = 0; + int step_limit = 200; + EdgeWeight input_cut = boundary.getEdgeCut(lhs, rhs); + EdgeWeight min_cut = input_cut; + PartitionID from = lhs; + PartitionID to = rhs; + + refinement_pq * queue = NULL; + refinement_pq * to_queue = NULL; + + int diff = 0; + + for(movements = 0; movements < (int)number_of_swaps; movements++) { + if( queue_lhs->empty() || queue_rhs->empty()) { + break; + } + if( stopping_rule->search_should_stop(min_cut_index, movements, step_limit) ) break; + + + Gain gain_lhs = queue_lhs->maxValue(); + Gain gain_rhs = queue_rhs->maxValue(); + + bool coin = false; + switch(config.kaba_lsearch_p) { + case COIN_DIFFTIE: + coin = random_functions::nextBool(); + if(coin) { + if( gain_rhs > gain_lhs ) { + queue = queue_rhs; + } else if ( gain_rhs < gain_lhs ) { + queue = queue_lhs; + } else { + queue = queue_lhs; } - - if(queue_lhs->empty() || queue_rhs->empty()) {delete queue_lhs; delete queue_rhs; return;} - // queues initalized - - ////roll forwards - int movements = 0; - int overall_gain = 0; - - kway_stop_rule* stopping_rule = new kway_simple_stop_rule(config); - - int min_cut_index = 0; - int step_limit = 200; - EdgeWeight input_cut = boundary.getEdgeCut(lhs, rhs); - EdgeWeight min_cut = input_cut; - PartitionID from = lhs; - PartitionID to = rhs; - - refinement_pq * queue = NULL; - refinement_pq * to_queue = NULL; - - int diff = 0; - - for(movements = 0; movements < (int)number_of_swaps; movements++) { - if( queue_lhs->empty() || queue_rhs->empty()) { - break; - } - if( stopping_rule->search_should_stop(min_cut_index, movements, step_limit) ) break; - - - Gain gain_lhs = queue_lhs->maxValue(); - Gain gain_rhs = queue_rhs->maxValue(); - - bool coin = false; - switch(config.kaba_lsearch_p) { - case COIN_DIFFTIE: - coin = random_functions::nextBool(); - if(coin) { - if( gain_rhs > gain_lhs ) { - queue = queue_rhs; - } else if ( gain_rhs < gain_lhs ) { - queue = queue_lhs; - } else { - queue = queue_lhs; - } - } else { - queue = queue_lhs; - } - break; - - case COIN_RNDTIE: - coin = random_functions::nextBool(); - if(coin) { - if( gain_rhs > gain_lhs ) { - queue = queue_rhs; - } else if ( gain_rhs < gain_lhs ) { - queue = queue_lhs; - } else { - coin = random_functions::nextBool(); - if(coin) { - queue = queue_rhs; - } else { - queue = queue_lhs; - } - } - } else { - queue = queue_lhs; - } - break; - case NOCOIN_DIFFTIE: - if( gain_rhs > gain_lhs ) { - queue = queue_rhs; - } else if ( gain_rhs < gain_lhs ) { - queue = queue_lhs; - } else { - queue = queue_lhs; - } - break; - - case NOCOIN_RNDTIE: - if( gain_rhs > gain_lhs ) { - queue = queue_rhs; - } else if ( gain_rhs < gain_lhs ) { - queue = queue_lhs; - } else { - coin = random_functions::nextBool(); - if(coin) { - queue = queue_rhs; - } else { - queue = queue_lhs; - } - } - break; - } - - NodeID node = queue->deleteMax(); - if( queue == queue_rhs ) { - from = rhs; - to = lhs; - gain = gain_rhs; - to_queue = queue_lhs; - diff += G.getNodeWeight(node); - } else { - from = lhs; - to = rhs; - gain = gain_lhs; - to_queue = queue_rhs; - diff -= G.getNodeWeight(node); - } - - - move_node(config, G, node, queue, to_queue, boundary, from, to); - - overall_gain += gain; - input_cut -= gain; - - stopping_rule->push_statistics(gain); - - if(input_cut < min_cut && diff == 0) { - min_cut = input_cut; - - pls.vertex_movements.clear(); - pls.block_movements.clear(); - pls.gains.clear(); - } else { - pls.vertex_movements.push_back(node); - pls.block_movements.push_back(to); - pls.gains.push_back(overall_gain); - } - m_tomake_eligible.push_back(node); + } else { + queue = queue_lhs; + } + break; + + case COIN_RNDTIE: + coin = random_functions::nextBool(); + if(coin) { + if( gain_rhs > gain_lhs ) { + queue = queue_rhs; + } else if ( gain_rhs < gain_lhs ) { + queue = queue_lhs; + } else { + coin = random_functions::nextBool(); + if(coin) { + queue = queue_rhs; + } else { + queue = queue_lhs; + } } - - ////roll backwards - int idx = pls.vertex_movements.size()-1; - //int idx = movements-1; - for(; idx >= 0; idx--) { - NodeID node = pls.vertex_movements[idx]; - PartitionID from = G.getPartitionIndex(node); - PartitionID to = from == lhs ? rhs : lhs; - perform_simple_move( config, G, boundary, node, from, to); - - //block the neighboring nodes to avoid conflicts - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if(m_eligible[target]) m_tomake_eligible.push_back(target); - m_eligible[target] = false; - } endfor + } else { + queue = queue_lhs; + } + break; + case NOCOIN_DIFFTIE: + if( gain_rhs > gain_lhs ) { + queue = queue_rhs; + } else if ( gain_rhs < gain_lhs ) { + queue = queue_lhs; + } else { + queue = queue_lhs; } + break; - delete queue_lhs; - delete queue_rhs; - delete stopping_rule; + case NOCOIN_RNDTIE: + if( gain_rhs > gain_lhs ) { + queue = queue_rhs; + } else if ( gain_rhs < gain_lhs ) { + queue = queue_lhs; + } else { + coin = random_functions::nextBool(); + if(coin) { + queue = queue_rhs; + } else { + queue = queue_lhs; + } + } + break; + } + + NodeID node = queue->deleteMax(); + if( queue == queue_rhs ) { + from = rhs; + to = lhs; + gain = gain_rhs; + to_queue = queue_lhs; + diff += G.getNodeWeight(node); + } else { + from = lhs; + to = rhs; + gain = gain_lhs; + to_queue = queue_rhs; + diff -= G.getNodeWeight(node); + } + + + move_node(config, G, node, queue, to_queue, boundary, from, to); + + overall_gain += gain; + input_cut -= gain; + + stopping_rule->push_statistics(gain); + + if(input_cut < min_cut && diff == 0) { + min_cut = input_cut; + + pls.vertex_movements.clear(); + pls.block_movements.clear(); + pls.gains.clear(); + } else { + pls.vertex_movements.push_back(node); + pls.block_movements.push_back(to); + pls.gains.push_back(overall_gain); + } + m_tomake_eligible.push_back(node); + } + + ////roll backwards + int idx = pls.vertex_movements.size()-1; + //int idx = movements-1; + for(; idx >= 0; idx--) { + NodeID node = pls.vertex_movements[idx]; + PartitionID from = G.getPartitionIndex(node); + PartitionID to = from == lhs ? rhs : lhs; + perform_simple_move( config, G, boundary, node, from, to); + + //block the neighboring nodes to avoid conflicts + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + if(m_eligible[target]) m_tomake_eligible.push_back(target); + m_eligible[target] = false; + } endfor } + delete queue_lhs; + delete queue_rhs; + delete stopping_rule; +} +} diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/augmented_Qgraph_fabric.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/augmented_Qgraph_fabric.h index 1ba44bf2..72b27f3c 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/augmented_Qgraph_fabric.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/augmented_Qgraph_fabric.h @@ -16,107 +16,107 @@ #include "uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_commons.h" #include "uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/two_way_fm.h" #include "uncoarsening/refinement/refinement.h" - +namespace kahip::modified { class augmented_Qgraph_fabric { - public: - augmented_Qgraph_fabric( ); - virtual ~augmented_Qgraph_fabric(); - - //return false if the network will be feasable for the desired model - //returns true iff rebalance = true and the fall back solution has been applied - bool build_augmented_quotient_graph( PartitionConfig & config, - graph_access & G, - complete_boundary & boundary, - augmented_Qgraph & aqg, - unsigned & s, bool rebalance, bool plus = false); - - void cleanup_eligible(); - - private: - bool construct_local_searches_on_qgraph_edge( PartitionConfig & config, - graph_access & G, - complete_boundary & boundary, - augmented_Qgraph & aqg, - boundary_pair & pair, - unsigned s, - bool plus); - - bool local_search(PartitionConfig & config, - bool plus, - graph_access & G, +public: + augmented_Qgraph_fabric( ); + virtual ~augmented_Qgraph_fabric(); + + //return false if the network will be feasable for the desired model + //returns true iff rebalance = true and the fall back solution has been applied + bool build_augmented_quotient_graph( PartitionConfig & config, + graph_access & G, + complete_boundary & boundary, + augmented_Qgraph & aqg, + unsigned & s, bool rebalance, bool plus = false); + + void cleanup_eligible(); + +private: + bool construct_local_searches_on_qgraph_edge( PartitionConfig & config, + graph_access & G, + complete_boundary & boundary, + augmented_Qgraph & aqg, + boundary_pair & pair, + unsigned s, + bool plus); + + bool local_search(PartitionConfig & config, + bool plus, + graph_access & G, + complete_boundary & boundary, + augmented_Qgraph & aqg, + boundary_pair & bp, + unsigned s); + + + void directed_more_locallized_search(PartitionConfig & config, graph_access & G, + complete_boundary & boundary, + PartitionID & lhs, PartitionID & rhs, + NodeID start_node, unsigned & number_of_swaps, pairwise_local_search & pls); + +public: + void more_locallized_search(PartitionConfig & config, graph_access & G, + complete_boundary & boundary, + PartitionID & lhs, PartitionID & rhs, + NodeID start_node, unsigned & number_of_swaps, pairwise_local_search & pls); +private: + void directed_more_locallized_search_all_bnd(PartitionConfig & config, graph_access & G, + complete_boundary & boundary, + PartitionID & lhs, PartitionID & rhs, + unsigned & number_of_swaps, pairwise_local_search & pls); + + void move_node(PartitionConfig & config, + graph_access & G, + NodeID & node, + refinement_pq * queue, + complete_boundary & boundary, + PartitionID & from, + PartitionID & to); + + void move_node(PartitionConfig & config, + graph_access & G, + NodeID & node, + refinement_pq * queue, + refinement_pq * to_queue, + complete_boundary & boundary, + PartitionID & from, + PartitionID & to); + + Gain find_eligible_start_node( graph_access & G, + PartitionID & lhs, + PartitionID & rhs, + std::vector & lhs_boundary, + std::vector & eligible, + NodeID & start_node, bool rebalance = false); + + void rebalance_fall_back(PartitionConfig & config, + graph_access & G, + graph_access & G_bar, + complete_boundary & boundary, + std::vector< NodeID > & candidates, + std::vector< int > & parent, + augmented_Qgraph & aqg); + + void perform_simple_move( PartitionConfig & config, + graph_access & G, complete_boundary & boundary, - augmented_Qgraph & aqg, - boundary_pair & bp, - unsigned s); - - - void directed_more_locallized_search(PartitionConfig & config, graph_access & G, - complete_boundary & boundary, - PartitionID & lhs, PartitionID & rhs, - NodeID start_node, unsigned & number_of_swaps, pairwise_local_search & pls); - - public: - void more_locallized_search(PartitionConfig & config, graph_access & G, - complete_boundary & boundary, - PartitionID & lhs, PartitionID & rhs, - NodeID start_node, unsigned & number_of_swaps, pairwise_local_search & pls); - private: - void directed_more_locallized_search_all_bnd(PartitionConfig & config, graph_access & G, - complete_boundary & boundary, - PartitionID & lhs, PartitionID & rhs, - unsigned & number_of_swaps, pairwise_local_search & pls); - - void move_node(PartitionConfig & config, - graph_access & G, - NodeID & node, - refinement_pq * queue, - complete_boundary & boundary, - PartitionID & from, - PartitionID & to); - - void move_node(PartitionConfig & config, - graph_access & G, - NodeID & node, - refinement_pq * queue, - refinement_pq * to_queue, - complete_boundary & boundary, - PartitionID & from, - PartitionID & to); - - Gain find_eligible_start_node( graph_access & G, - PartitionID & lhs, - PartitionID & rhs, - std::vector & lhs_boundary, - std::vector & eligible, - NodeID & start_node, bool rebalance = false); - - void rebalance_fall_back(PartitionConfig & config, - graph_access & G, - graph_access & G_bar, - complete_boundary & boundary, - std::vector< NodeID > & candidates, - std::vector< int > & parent, - augmented_Qgraph & aqg); - - void perform_simple_move( PartitionConfig & config, - graph_access & G, - complete_boundary & boundary, - NodeID & node, - PartitionID & from, - PartitionID & to); - - kway_graph_refinement_commons* commons; - two_way_fm m_twfm; - std::vector m_eligible; - std::vector m_tomake_eligible; + NodeID & node, + PartitionID & from, + PartitionID & to); + + kway_graph_refinement_commons* commons; + two_way_fm m_twfm; + std::vector m_eligible; + std::vector m_tomake_eligible; }; -inline -Gain augmented_Qgraph_fabric::find_eligible_start_node( graph_access & G, - PartitionID & lhs, - PartitionID & rhs, - std::vector & lhs_boundary, +inline +Gain augmented_Qgraph_fabric::find_eligible_start_node( graph_access & G, + PartitionID & lhs, + PartitionID & rhs, + std::vector & lhs_boundary, std::vector & eligible_, NodeID & start_node, bool rebalance) { //select start node @@ -136,8 +136,8 @@ Gain augmented_Qgraph_fabric::find_eligible_start_node( graph_access & G, m_twfm.int_ext_degree(G, node, lhs, rhs, int_degree, ext_degree); if( ext_degree - int_degree > max_gain) { //todo tiebreaking max_gain = ext_degree - int_degree; - } - } + } + } } if( max_gain == std::numeric_limits::min() ) { @@ -152,14 +152,14 @@ Gain augmented_Qgraph_fabric::find_eligible_start_node( graph_access & G, EdgeWeight int_degree = 0; EdgeWeight ext_degree = 0; m_twfm.int_ext_degree(G, node, lhs, rhs, int_degree, ext_degree); - if( ext_degree - int_degree == max_gain) { - eligibles.push_back(node); - } - } + if( ext_degree - int_degree == max_gain) { + eligibles.push_back(node); + } + } } random_idx = random_functions::nextInt(0, eligibles.size()-1); - start_node = eligibles[random_idx]; + start_node = eligibles[random_idx]; for( unsigned i = 0; i < lhs_boundary.size(); i++) { NodeID node = lhs_boundary[i]; @@ -175,15 +175,15 @@ Gain augmented_Qgraph_fabric::find_eligible_start_node( graph_access & G, } inline -void augmented_Qgraph_fabric::rebalance_fall_back(PartitionConfig & config, - graph_access & G, - graph_access & G_bar, - complete_boundary & boundary, - std::vector< NodeID > & candidates, +void augmented_Qgraph_fabric::rebalance_fall_back(PartitionConfig & config, + graph_access & G, + graph_access & G_bar, + complete_boundary & boundary, + std::vector< NodeID > & candidates, std::vector< int > & parent, augmented_Qgraph & aqg) { - std::vector eligible_(G.number_of_nodes(), true); + std::vector eligible_(G.number_of_nodes(), true); random_functions::permutate_vector_good_small(candidates); std::vector best_path; @@ -228,17 +228,17 @@ void augmented_Qgraph_fabric::rebalance_fall_back(PartitionConfig & config, perform_simple_move(config, G, boundary, node, lhs, rhs); } - } + } - //undo these changes - for( unsigned i = 0; i < cur_path.size(); i++) { - perform_simple_move(config, G, boundary, cur_path[i].node, cur_path[i].to, cur_path[i].from); - } + //undo these changes + for( unsigned i = 0; i < cur_path.size(); i++) { + perform_simple_move(config, G, boundary, cur_path[i].node, cur_path[i].to, cur_path[i].from); + } - if(cur_path_gain > best_path_gain) { - best_path = cur_path; - best_path_gain = cur_path_gain; - } + if(cur_path_gain > best_path_gain) { + best_path = cur_path; + best_path_gain = cur_path_gain; + } } @@ -407,7 +407,7 @@ bool augmented_Qgraph_fabric::local_search(PartitionConfig & config, unsigned s ) { return construct_local_searches_on_qgraph_edge( config, G, boundary, aqg, pair, s, plus); } - +} #endif /* end of include guard: AUGMENTED_QGRAPH_FABRIC_MULTITRY_FM_PVGY97EW*/ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/cycle_definitions.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/cycle_definitions.h index f9185c2d..a660a02b 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/cycle_definitions.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/cycle_definitions.h @@ -11,7 +11,7 @@ #include #include "uncoarsening/refinement/quotient_graph_refinement/complete_boundary.h" - +namespace kahip::modified { struct undo_struct { NodeID node; PartitionID to; @@ -28,6 +28,6 @@ struct data_qgraph_edge { }; typedef std::unordered_map edge_movements; - +} #endif /* end of include guard: DEFINITIONS_4GQMW8PZ */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/cycle_refinement.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/cycle_refinement.cpp index 9834a433..e8d06c0b 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/cycle_refinement.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/cycle_refinement.cpp @@ -11,7 +11,7 @@ #include "augmented_Qgraph_fabric.h" #include "cycle_refinement.h" #include "quality_metrics.h" - +namespace kahip::modified { cycle_refinement::cycle_refinement() { } @@ -23,170 +23,171 @@ cycle_refinement::~cycle_refinement() { EdgeWeight cycle_refinement::perform_refinement(PartitionConfig & partition_config, graph_access & G, complete_boundary & boundary) { - Gain overall_gain = 0; - PartitionConfig copy = partition_config; - - switch(partition_config.cycle_refinement_algorithm) { - case CYCLE_REFINEMENT_ALGORITHM_ULTRA_MODEL: - overall_gain = greedy_ultra_model(copy, G, boundary); - break; - case CYCLE_REFINEMENT_ALGORITHM_ULTRA_MODEL_PLUS: - overall_gain = greedy_ultra_model_plus(copy, G, boundary); - break; - case CYCLE_REFINEMENT_ALGORITHM_PLAYFIELD: - //dropbox for new algorithms - overall_gain = playfield_algorithm(copy, G, boundary); - break; - } - - return overall_gain; + Gain overall_gain = 0; + PartitionConfig copy = partition_config; + + switch(partition_config.cycle_refinement_algorithm) { + case CYCLE_REFINEMENT_ALGORITHM_ULTRA_MODEL: + overall_gain = greedy_ultra_model(copy, G, boundary); + break; + case CYCLE_REFINEMENT_ALGORITHM_ULTRA_MODEL_PLUS: + overall_gain = greedy_ultra_model_plus(copy, G, boundary); + break; + case CYCLE_REFINEMENT_ALGORITHM_PLAYFIELD: + //dropbox for new algorithms + overall_gain = playfield_algorithm(copy, G, boundary); + break; + } + + return overall_gain; } -EdgeWeight cycle_refinement::playfield_algorithm(PartitionConfig & partition_config, - graph_access & G, +EdgeWeight cycle_refinement::playfield_algorithm(PartitionConfig & partition_config, + graph_access & G, complete_boundary & boundary) { - greedy_ultra_model(partition_config, G, boundary); - greedy_ultra_model_plus(partition_config, G, boundary); - return 0; + greedy_ultra_model(partition_config, G, boundary); + greedy_ultra_model_plus(partition_config, G, boundary); + return 0; } -EdgeWeight cycle_refinement::greedy_ultra_model(PartitionConfig & partition_config, - graph_access & G, +EdgeWeight cycle_refinement::greedy_ultra_model(PartitionConfig & partition_config, + graph_access & G, complete_boundary & boundary) { - augmented_Qgraph_fabric augmented_fabric; - unsigned s = partition_config.kaba_internal_no_aug_steps_aug; - bool something_changed = false; - bool overloaded = false; - unsigned unsucc_count = 0; - - do { - augmented_Qgraph aqg; - augmented_fabric.build_augmented_quotient_graph(partition_config, G, boundary, aqg, s, false); - something_changed = m_advanced_modelling.compute_vertex_movements_ultra_model(partition_config, - G, - boundary, - aqg, - s, - false); - if( something_changed ) { - unsucc_count = 0; - } else { - unsucc_count++; - } - - if(unsucc_count > 2 - && unsucc_count <= partition_config.kaba_unsucc_iterations - && partition_config.kaba_enable_zero_weight_cycles) { - something_changed = m_advanced_modelling.compute_vertex_movements_ultra_model(partition_config, - G, - boundary, - aqg, - s, - true); - } - - if(unsucc_count >= partition_config.kaba_unsucc_iterations ) { - graph_access G_bar; - boundary.getUnderlyingQuotientGraph(G_bar); - overloaded = false; - forall_nodes(G_bar, block) { - if(boundary.getBlockWeight(block) > partition_config.upper_bound_partition ) { - overloaded = true; - break; - } - } endfor - - if(overloaded) { - augmented_Qgraph aqg_rebal; - bool movs_allready_performed = augmented_fabric.build_augmented_quotient_graph(partition_config, - G, - boundary, - aqg_rebal, - s, true); - if(!movs_allready_performed) { - m_advanced_modelling.compute_vertex_movements_rebalance(partition_config, - G, - boundary, - aqg_rebal, s); - } // else the fall back solution has been applied - } - } - } while(unsucc_count < partition_config.kaba_unsucc_iterations || (overloaded)); - - return 0; + augmented_Qgraph_fabric augmented_fabric; + unsigned s = partition_config.kaba_internal_no_aug_steps_aug; + bool something_changed = false; + bool overloaded = false; + unsigned unsucc_count = 0; + + do { + augmented_Qgraph aqg; + augmented_fabric.build_augmented_quotient_graph(partition_config, G, boundary, aqg, s, false); + something_changed = m_advanced_modelling.compute_vertex_movements_ultra_model(partition_config, + G, + boundary, + aqg, + s, + false); + if( something_changed ) { + unsucc_count = 0; + } else { + unsucc_count++; + } + + if(unsucc_count > 2 + && unsucc_count <= partition_config.kaba_unsucc_iterations + && partition_config.kaba_enable_zero_weight_cycles) { + something_changed = m_advanced_modelling.compute_vertex_movements_ultra_model(partition_config, + G, + boundary, + aqg, + s, + true); + } + + if(unsucc_count >= partition_config.kaba_unsucc_iterations ) { + graph_access G_bar; + boundary.getUnderlyingQuotientGraph(G_bar); + overloaded = false; + forall_nodes(G_bar, block) { + if(boundary.getBlockWeight(block) > partition_config.upper_bound_partition ) { + overloaded = true; + break; + } + } endfor + + if(overloaded) { + augmented_Qgraph aqg_rebal; + bool movs_allready_performed = augmented_fabric.build_augmented_quotient_graph(partition_config, + G, + boundary, + aqg_rebal, + s, true); + if(!movs_allready_performed) { + m_advanced_modelling.compute_vertex_movements_rebalance(partition_config, + G, + boundary, + aqg_rebal, s); + } // else the fall back solution has been applied + } + } + } while(unsucc_count < partition_config.kaba_unsucc_iterations || (overloaded)); + + return 0; } -EdgeWeight cycle_refinement::greedy_ultra_model_plus(PartitionConfig & partition_config, - graph_access & G, +EdgeWeight cycle_refinement::greedy_ultra_model_plus(PartitionConfig & partition_config, + graph_access & G, complete_boundary & boundary) { - unsigned s = partition_config.kaba_internal_no_aug_steps_aug; - bool something_changed = false; - bool overloaded = false; - - - augmented_Qgraph_fabric augmented_fabric; - bool first_level = true; - forall_nodes(G, node) { - if(G.getNodeWeight(node) != 1) { - first_level = false; - break; - } - } endfor - - int unsucc_count = 0; - do { - augmented_Qgraph aqg; - augmented_fabric.build_augmented_quotient_graph(partition_config, G, boundary, aqg, s, false, true); - something_changed = m_advanced_modelling.compute_vertex_movements_ultra_model(partition_config, - G, - boundary, - aqg, - s, - false); - - if( something_changed ) { - unsucc_count = 0; - } else { - unsucc_count++; - } - - if(unsucc_count > 2 && unsucc_count < 19) { - something_changed = m_advanced_modelling.compute_vertex_movements_ultra_model(partition_config, - G, - boundary, - aqg, - s, true); - } - - if(unsucc_count > 19 && first_level) { - graph_access G_bar; - boundary.getUnderlyingQuotientGraph(G_bar); - overloaded = false; - forall_nodes(G_bar, block) { - if(boundary.getBlockWeight(block) > partition_config.upper_bound_partition ) { - overloaded = true; - break; - } - } endfor - - if(overloaded) { - augmented_Qgraph aqg_rebal; - bool moves_performed = augmented_fabric.build_augmented_quotient_graph(partition_config, - G, - boundary, - aqg_rebal, - s, true, true); - if(!moves_performed) { - m_advanced_modelling.compute_vertex_movements_rebalance(partition_config, - G, boundary, - aqg_rebal, s); - } // else the fall back solution has been applied - } - - } - } while(unsucc_count < 20 || overloaded); - - return 0; + unsigned s = partition_config.kaba_internal_no_aug_steps_aug; + bool something_changed = false; + bool overloaded = false; + + + augmented_Qgraph_fabric augmented_fabric; + bool first_level = true; + forall_nodes(G, node) { + if(G.getNodeWeight(node) != 1) { + first_level = false; + break; + } + } endfor + + int unsucc_count = 0; + do { + augmented_Qgraph aqg; + augmented_fabric.build_augmented_quotient_graph(partition_config, G, boundary, aqg, s, false, true); + something_changed = m_advanced_modelling.compute_vertex_movements_ultra_model(partition_config, + G, + boundary, + aqg, + s, + false); + + if( something_changed ) { + unsucc_count = 0; + } else { + unsucc_count++; + } + + if(unsucc_count > 2 && unsucc_count < 19) { + something_changed = m_advanced_modelling.compute_vertex_movements_ultra_model(partition_config, + G, + boundary, + aqg, + s, true); + } + + if(unsucc_count > 19 && first_level) { + graph_access G_bar; + boundary.getUnderlyingQuotientGraph(G_bar); + overloaded = false; + forall_nodes(G_bar, block) { + if(boundary.getBlockWeight(block) > partition_config.upper_bound_partition ) { + overloaded = true; + break; + } + } endfor + + if(overloaded) { + augmented_Qgraph aqg_rebal; + bool moves_performed = augmented_fabric.build_augmented_quotient_graph(partition_config, + G, + boundary, + aqg_rebal, + s, true, true); + if(!moves_performed) { + m_advanced_modelling.compute_vertex_movements_rebalance(partition_config, + G, boundary, + aqg_rebal, s); + } // else the fall back solution has been applied + } + + } + } while(unsucc_count < 20 || overloaded); + + return 0; } +} \ No newline at end of file diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/cycle_refinement.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/cycle_refinement.h index 8bd94fed..7f56041c 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/cycle_refinement.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/cycle_refinement.h @@ -15,32 +15,32 @@ #include "random_functions.h" #include "uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_commons.h" #include "uncoarsening/refinement/refinement.h" - +namespace kahip::modified { class cycle_refinement : public refinement{ - public: - cycle_refinement(); - virtual ~cycle_refinement(); +public: + cycle_refinement(); + virtual ~cycle_refinement(); - EdgeWeight perform_refinement(PartitionConfig & config, - graph_access & G, - complete_boundary & boundary); + EdgeWeight perform_refinement(PartitionConfig & config, + graph_access & G, + complete_boundary & boundary); - private: - EdgeWeight greedy_ultra_model(PartitionConfig & partition_config, - graph_access & G, - complete_boundary & boundary); +private: + EdgeWeight greedy_ultra_model(PartitionConfig & partition_config, + graph_access & G, + complete_boundary & boundary); - EdgeWeight greedy_ultra_model_plus(PartitionConfig & partition_config, - graph_access & G, - complete_boundary & boundary); + EdgeWeight greedy_ultra_model_plus(PartitionConfig & partition_config, + graph_access & G, + complete_boundary & boundary); - EdgeWeight playfield_algorithm(PartitionConfig & partition_config, - graph_access & G, - complete_boundary & boundary); + EdgeWeight playfield_algorithm(PartitionConfig & partition_config, + graph_access & G, + complete_boundary & boundary); - advanced_models m_advanced_modelling; + advanced_models m_advanced_modelling; }; - +} diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/greedy_neg_cycle.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/greedy_neg_cycle.cpp index 2c937261..3642d7e8 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/greedy_neg_cycle.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/greedy_neg_cycle.cpp @@ -6,7 +6,7 @@ *****************************************************************************/ #include "greedy_neg_cycle.h" - +namespace kahip::modified { greedy_neg_cycle::greedy_neg_cycle() { } @@ -14,4 +14,4 @@ greedy_neg_cycle::greedy_neg_cycle() { greedy_neg_cycle::~greedy_neg_cycle() { } - +} diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/greedy_neg_cycle.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/greedy_neg_cycle.h index dc780943..7baea72a 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/greedy_neg_cycle.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/greedy_neg_cycle.h @@ -16,7 +16,7 @@ #include "problem_factory.h" #include "uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_commons.h" #include "uncoarsening/refinement/quotient_graph_refinement/complete_boundary.h" - +namespace kahip::modified { class greedy_neg_cycle { public: greedy_neg_cycle(); @@ -283,5 +283,5 @@ inline void greedy_neg_cycle::init_gains( PartitionConfig & partition_config, } - +} #endif /* end of include guard: GREEDY_NEG_CYCLE_IVBKH6WD */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/problem_factory.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/problem_factory.cpp index dbafc9bf..20e478fb 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/problem_factory.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/problem_factory.cpp @@ -6,7 +6,7 @@ *****************************************************************************/ #include "problem_factory.h" - +namespace kahip::modified { problem_factory::problem_factory() { } @@ -14,4 +14,4 @@ problem_factory::problem_factory() { problem_factory::~problem_factory() { } - +} \ No newline at end of file diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/problem_factory.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/problem_factory.h index 7edb395b..bcda5f0b 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/problem_factory.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/cycle_improvements/problem_factory.h @@ -11,30 +11,30 @@ #include "definitions.h" #include "partition_config.h" #include "uncoarsening/refinement/quotient_graph_refinement/complete_boundary.h" - +namespace kahip::modified { class problem_factory { - public: - problem_factory(); - virtual ~problem_factory(); - - void build_cycle_problem( PartitionConfig & partition_config, - complete_boundary & boundary, - graph_access & G_bar, - graph_access & cycle_problem, - NodeID & s); - - void build_cycle_problem_with_reverse( PartitionConfig & partition_config, - complete_boundary & boundary, - graph_access & G_bar, - graph_access & cycle_problem, - NodeID & s); - - void build_shortest_path_problem( PartitionConfig & partition_config, - complete_boundary & boundary, - graph_access & G_bar, - graph_access & shortest_path_problem, - NodeID & s, - NodeID & t); +public: + problem_factory(); + virtual ~problem_factory(); + + void build_cycle_problem( PartitionConfig & partition_config, + complete_boundary & boundary, + graph_access & G_bar, + graph_access & cycle_problem, + NodeID & s); + + void build_cycle_problem_with_reverse( PartitionConfig & partition_config, + complete_boundary & boundary, + graph_access & G_bar, + graph_access & cycle_problem, + NodeID & s); + + void build_shortest_path_problem( PartitionConfig & partition_config, + complete_boundary & boundary, + graph_access & G_bar, + graph_access & shortest_path_problem, + NodeID & s, + NodeID & t); }; @@ -139,5 +139,5 @@ void problem_factory::build_shortest_path_problem( PartitionConfig & partition_c shortest_path_problem.finish_construction(); } - +} #endif /* end of include guard: PROBLEM_FACTORY_KHGQXT9H */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement.cpp index f2a419ea..d1b8a24f 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement.cpp @@ -13,7 +13,7 @@ #include "kway_stop_rule.h" #include "quality_metrics.h" #include "random_functions.h" - +namespace kahip::modified { kway_graph_refinement::kway_graph_refinement() { } @@ -24,70 +24,70 @@ kway_graph_refinement::~kway_graph_refinement() { EdgeWeight kway_graph_refinement::perform_refinement(PartitionConfig & config, graph_access & G, complete_boundary & boundary) { - commons = kway_graph_refinement_commons::getInstance(config); - kway_graph_refinement_core refinement_core; - - EdgeWeight overall_improvement = 0; - int max_number_of_swaps = (int)(G.number_of_nodes()); - bool sth_changed = config.no_change_convergence; + commons = kway_graph_refinement_commons::getInstance(config); + kway_graph_refinement_core refinement_core; + + EdgeWeight overall_improvement = 0; + int max_number_of_swaps = (int)(G.number_of_nodes()); + bool sth_changed = config.no_change_convergence; - for( unsigned i = 0; i < config.kway_rounds || sth_changed; i++) { - EdgeWeight improvement = 0; + for( unsigned i = 0; i < config.kway_rounds || sth_changed; i++) { + EdgeWeight improvement = 0; - boundary_starting_nodes start_nodes; - setup_start_nodes(config, G, boundary, start_nodes); + boundary_starting_nodes start_nodes; + setup_start_nodes(config, G, boundary, start_nodes); - if(start_nodes.size() == 0) return 0; // nothing to refine + if(start_nodes.size() == 0) return 0; // nothing to refine - //metis steplimit - int step_limit = (int)((config.kway_fm_search_limit/100.0)*max_number_of_swaps); - step_limit = std::max(step_limit, 15); + //metis steplimit + int step_limit = (int)((config.kway_fm_search_limit/100.0)*max_number_of_swaps); + step_limit = std::max(step_limit, 15); - vertex_moved_hashtable moved_idx; - improvement += refinement_core.single_kway_refinement_round(config, G, boundary, - start_nodes, step_limit, - moved_idx); + vertex_moved_hashtable moved_idx; + improvement += refinement_core.single_kway_refinement_round(config, G, boundary, + start_nodes, step_limit, + moved_idx); - sth_changed = improvement != 0 && config.no_change_convergence; - if(improvement == 0) break; - overall_improvement += improvement; + sth_changed = improvement != 0 && config.no_change_convergence; + if(improvement == 0) break; + overall_improvement += improvement; - } + } - ASSERT_TRUE(overall_improvement >= 0); + ASSERT_TRUE(overall_improvement >= 0); - return (EdgeWeight) overall_improvement; + return (EdgeWeight) overall_improvement; } void kway_graph_refinement::setup_start_nodes(PartitionConfig & config, graph_access & G, complete_boundary & boundary, boundary_starting_nodes & start_nodes) { - QuotientGraphEdges quotient_graph_edges; - boundary.getQuotientGraphEdges(quotient_graph_edges); - - std::unordered_map allready_contained; - - for( unsigned i = 0; i < quotient_graph_edges.size(); i++) { - boundary_pair & ret_value = quotient_graph_edges[i]; - PartitionID lhs = ret_value.lhs; - PartitionID rhs = ret_value.rhs; - - PartialBoundary & partial_boundary_lhs = boundary.getDirectedBoundary(lhs, lhs, rhs); - forall_boundary_nodes(partial_boundary_lhs, cur_bnd_node) { - ASSERT_EQ(G.getPartitionIndex(cur_bnd_node), lhs); - if(allready_contained.find(cur_bnd_node) == allready_contained.end() ) { - start_nodes.push_back(cur_bnd_node); - allready_contained[cur_bnd_node] = true; - } - } endfor - - PartialBoundary & partial_boundary_rhs = boundary.getDirectedBoundary(rhs, lhs, rhs); - forall_boundary_nodes(partial_boundary_rhs, cur_bnd_node) { - ASSERT_EQ(G.getPartitionIndex(cur_bnd_node), rhs); - if(allready_contained.find(cur_bnd_node) == allready_contained.end()) { - start_nodes.push_back(cur_bnd_node); - allready_contained[cur_bnd_node] = true; - } - } endfor - } + QuotientGraphEdges quotient_graph_edges; + boundary.getQuotientGraphEdges(quotient_graph_edges); + + std::unordered_map allready_contained; + + for( unsigned i = 0; i < quotient_graph_edges.size(); i++) { + boundary_pair & ret_value = quotient_graph_edges[i]; + PartitionID lhs = ret_value.lhs; + PartitionID rhs = ret_value.rhs; + + PartialBoundary & partial_boundary_lhs = boundary.getDirectedBoundary(lhs, lhs, rhs); + forall_boundary_nodes(partial_boundary_lhs, cur_bnd_node) { + ASSERT_EQ(G.getPartitionIndex(cur_bnd_node), lhs); + if(allready_contained.find(cur_bnd_node) == allready_contained.end() ) { + start_nodes.push_back(cur_bnd_node); + allready_contained[cur_bnd_node] = true; + } + } endfor + + PartialBoundary & partial_boundary_rhs = boundary.getDirectedBoundary(rhs, lhs, rhs); + forall_boundary_nodes(partial_boundary_rhs, cur_bnd_node) { + ASSERT_EQ(G.getPartitionIndex(cur_bnd_node), rhs); + if(allready_contained.find(cur_bnd_node) == allready_contained.end()) { + start_nodes.push_back(cur_bnd_node); + allready_contained[cur_bnd_node] = true; + } + } endfor +} +} } - diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement.h index 5d4bb124..6319df2e 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement.h @@ -16,25 +16,25 @@ #include "random_functions.h" #include "uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/vertex_moved_hashtable.h" #include "uncoarsening/refinement/refinement.h" - +namespace kahip::modified { class kway_graph_refinement : public refinement { - public: - kway_graph_refinement( ); - virtual ~kway_graph_refinement(); - - EdgeWeight perform_refinement(PartitionConfig & config, - graph_access & G, - complete_boundary & boundary); - - void setup_start_nodes(PartitionConfig & config, - graph_access & G, - complete_boundary & boundary, - boundary_starting_nodes & start_nodes); - - private: - - kway_graph_refinement_commons* commons; -}; +public: + kway_graph_refinement( ); + virtual ~kway_graph_refinement(); + + EdgeWeight perform_refinement(PartitionConfig & config, + graph_access & G, + complete_boundary & boundary); + void setup_start_nodes(PartitionConfig & config, + graph_access & G, + complete_boundary & boundary, + boundary_starting_nodes & start_nodes); + +private: + + kway_graph_refinement_commons* commons; +}; +} #endif /* end of include guard: KWAY_GRAPH_REFINEMENT_PVGY97EW */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_commons.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_commons.cpp index bf21b630..f1b5d7b9 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_commons.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_commons.cpp @@ -10,7 +10,7 @@ #endif #include "kway_graph_refinement_commons.h" - +namespace kahip::modified { std::vector* kway_graph_refinement_commons::m_instances = NULL; kway_graph_refinement_commons::kway_graph_refinement_commons() { @@ -22,35 +22,36 @@ kway_graph_refinement_commons::~kway_graph_refinement_commons() { kway_graph_refinement_commons* kway_graph_refinement_commons::getInstance( PartitionConfig & config ) { - bool created = false; - #ifdef USE_OPENMP - int max_threads = omp_get_max_threads(); - #pragma omp critical - #else - int max_threads = 1; - #endif - { - if( m_instances == NULL ) { - m_instances = new std::vector< kway_graph_refinement_commons*>(max_threads, NULL); - } - } - #ifdef USE_OPENMP - int id = omp_get_thread_num(); - #else - int id = 0; - #endif - if((*m_instances)[id] == NULL) { - (*m_instances)[id] = new kway_graph_refinement_commons(); - (*m_instances)[id]->init(config); - created = true; - } - - if(created == false) { - if(config.k != (*m_instances)[id]->getUnderlyingK()) { - //should be a very rare case - (*m_instances)[id]->init(config); - } - } - - return (*m_instances)[id]; + bool created = false; +#ifdef USE_OPENMP + int max_threads = omp_get_max_threads(); +#pragma omp critical +#else + int max_threads = 1; +#endif + { + if( m_instances == NULL ) { + m_instances = new std::vector< kway_graph_refinement_commons*>(max_threads, NULL); + } + } +#ifdef USE_OPENMP + int id = omp_get_thread_num(); +#else + int id = 0; +#endif + if((*m_instances)[id] == NULL) { + (*m_instances)[id] = new kway_graph_refinement_commons(); + (*m_instances)[id]->init(config); + created = true; + } + + if(created == false) { + if(config.k != (*m_instances)[id]->getUnderlyingK()) { + //should be a very rare case + (*m_instances)[id]->init(config); + } + } + + return (*m_instances)[id]; } +} \ No newline at end of file diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_commons.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_commons.h index 6b39d9fe..a95a4b05 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_commons.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_commons.h @@ -16,45 +16,45 @@ #include "random_functions.h" #include "uncoarsening/refinement/refinement.h" #include "uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/vertex_moved_hashtable.h" - +namespace kahip::modified { class kway_graph_refinement_commons { - public: +public: - virtual ~kway_graph_refinement_commons(); + virtual ~kway_graph_refinement_commons(); - void init( PartitionConfig & config ); + void init( PartitionConfig & config ); - bool incident_to_more_than_two_partitions(graph_access & G, NodeID & node); + bool incident_to_more_than_two_partitions(graph_access & G, NodeID & node); - EdgeWeight compute_gain(graph_access & G, - NodeID & node, - PartitionID & max_gainer, - EdgeWeight & ext_degree); + EdgeWeight compute_gain(graph_access & G, + NodeID & node, + PartitionID & max_gainer, + EdgeWeight & ext_degree); - bool int_ext_degree( graph_access & G, - const NodeID & node, - const PartitionID lhs, - const PartitionID rhs, - EdgeWeight & int_degree, - EdgeWeight & ext_degree); + bool int_ext_degree( graph_access & G, + const NodeID & node, + const PartitionID lhs, + const PartitionID rhs, + EdgeWeight & int_degree, + EdgeWeight & ext_degree); - static kway_graph_refinement_commons* getInstance( PartitionConfig & config ); + static kway_graph_refinement_commons* getInstance( PartitionConfig & config ); - inline unsigned getUnderlyingK(); + inline unsigned getUnderlyingK(); - private: - kway_graph_refinement_commons( ); +private: + kway_graph_refinement_commons( ); - //for efficient computation of internal and external degrees - struct round_struct { - unsigned round; - EdgeWeight local_degree; - }; + //for efficient computation of internal and external degrees + struct round_struct { + unsigned round; + EdgeWeight local_degree; + }; - static - std::vector* m_instances; - std::vector m_local_degrees; - unsigned m_round; + static + std::vector* m_instances; + std::vector m_local_degrees; + unsigned m_round; }; @@ -178,7 +178,7 @@ inline Gain kway_graph_refinement_commons::compute_gain(graph_access & G, return max_degree-m_local_degrees[source_partition].local_degree; } - +} #endif /* end of include guard: KWAY_GRAPH_REFINEMENT_COMMONS_PVGY97EW */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_core.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_core.cpp index 076f918e..8ffc6e38 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_core.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_core.cpp @@ -13,7 +13,7 @@ #include "kway_stop_rule.h" #include "quality_metrics.h" #include "random_functions.h" - +namespace kahip::modified { kway_graph_refinement_core::kway_graph_refinement_core() { } @@ -26,147 +26,147 @@ EdgeWeight kway_graph_refinement_core::single_kway_refinement_round(PartitionCon boundary_starting_nodes & start_nodes, int step_limit, vertex_moved_hashtable & moved_idx) { - std::unordered_map touched_blocks; - return single_kway_refinement_round_internal(config, G, boundary, start_nodes, - step_limit, moved_idx, false, touched_blocks); + std::unordered_map touched_blocks; + return single_kway_refinement_round_internal(config, G, boundary, start_nodes, + step_limit, moved_idx, false, touched_blocks); } -EdgeWeight kway_graph_refinement_core::single_kway_refinement_round(PartitionConfig & config, - graph_access & G, - complete_boundary & boundary, - boundary_starting_nodes & start_nodes, - int step_limit, +EdgeWeight kway_graph_refinement_core::single_kway_refinement_round(PartitionConfig & config, + graph_access & G, + complete_boundary & boundary, + boundary_starting_nodes & start_nodes, + int step_limit, vertex_moved_hashtable & moved_idx, std::unordered_map & touched_blocks) { - return single_kway_refinement_round_internal(config, G, boundary, start_nodes, - step_limit, moved_idx, true, touched_blocks); + return single_kway_refinement_round_internal(config, G, boundary, start_nodes, + step_limit, moved_idx, true, touched_blocks); } -EdgeWeight kway_graph_refinement_core::single_kway_refinement_round_internal(PartitionConfig & config, - graph_access & G, - complete_boundary & boundary, - boundary_starting_nodes & start_nodes, +EdgeWeight kway_graph_refinement_core::single_kway_refinement_round_internal(PartitionConfig & config, + graph_access & G, + complete_boundary & boundary, + boundary_starting_nodes & start_nodes, int step_limit, vertex_moved_hashtable & moved_idx, bool compute_touched_partitions, std::unordered_map & touched_blocks) { - commons = kway_graph_refinement_commons::getInstance(config); - refinement_pq* queue = NULL; - if(config.use_bucket_queues) { - EdgeWeight max_degree = G.getMaxDegree(); - queue = new bucket_pq(max_degree); - } else { - queue = new maxNodeHeap(); - } + commons = kway_graph_refinement_commons::getInstance(config); + refinement_pq* queue = NULL; + if(config.use_bucket_queues) { + EdgeWeight max_degree = G.getMaxDegree(); + queue = new bucket_pq(max_degree); + } else { + queue = new maxNodeHeap(); + } + + init_queue_with_boundary(config, G, start_nodes, queue, moved_idx); - init_queue_with_boundary(config, G, start_nodes, queue, moved_idx); - - if(queue->empty()) {delete queue; return 0;} + if(queue->empty()) {delete queue; return 0;} - std::vector transpositions; - std::vector from_partitions; - std::vector to_partitions; + std::vector transpositions; + std::vector from_partitions; + std::vector to_partitions; - int max_number_of_swaps = (int)(G.number_of_nodes()); - int min_cut_index = -1; + int max_number_of_swaps = (int)(G.number_of_nodes()); + int min_cut_index = -1; - EdgeWeight cut = std::numeric_limits::max()/2; // so we dont need to compute the edge cut - EdgeWeight initial_cut = cut; + EdgeWeight cut = std::numeric_limits::max()/2; // so we dont need to compute the edge cut + EdgeWeight initial_cut = cut; - //roll forwards - EdgeWeight best_cut = cut; - int number_of_swaps = 0; - int movements = 0; + //roll forwards + EdgeWeight best_cut = cut; + int number_of_swaps = 0; + int movements = 0; - kway_stop_rule* stopping_rule = NULL; - switch(config.kway_stop_rule) { - case KWAY_SIMPLE_STOP_RULE: - stopping_rule = new kway_simple_stop_rule(config); - break; - case KWAY_ADAPTIVE_STOP_RULE: - stopping_rule = new kway_adaptive_stop_rule(config); - break; + kway_stop_rule* stopping_rule = NULL; + switch(config.kway_stop_rule) { + case KWAY_SIMPLE_STOP_RULE: + stopping_rule = new kway_simple_stop_rule(config); + break; + case KWAY_ADAPTIVE_STOP_RULE: + stopping_rule = new kway_adaptive_stop_rule(config); + break; - } + } - for(number_of_swaps = 0, movements = 0; movements < max_number_of_swaps; movements++, number_of_swaps++) { - if( queue->empty() ) break; - if( stopping_rule->search_should_stop(min_cut_index, number_of_swaps, step_limit) ) break; + for(number_of_swaps = 0, movements = 0; movements < max_number_of_swaps; movements++, number_of_swaps++) { + if( queue->empty() ) break; + if( stopping_rule->search_should_stop(min_cut_index, number_of_swaps, step_limit) ) break; - Gain gain = queue->maxValue(); - NodeID node = queue->deleteMax(); + Gain gain = queue->maxValue(); + NodeID node = queue->deleteMax(); #ifndef NDEBUG - PartitionID maxgainer; - EdgeWeight ext_degree; - ASSERT_TRUE(moved_idx[node].index == NOT_MOVED); - ASSERT_EQ(gain, commons->compute_gain(G, node, maxgainer, ext_degree)); - ASSERT_TRUE(ext_degree > 0); + PartitionID maxgainer; + EdgeWeight ext_degree; + ASSERT_TRUE(moved_idx[node].index == NOT_MOVED); + ASSERT_EQ(gain, commons->compute_gain(G, node, maxgainer, ext_degree)); + ASSERT_TRUE(ext_degree > 0); #endif - PartitionID from = G.getPartitionIndex(node); - bool successfull = move_node(config, G, node, moved_idx, queue, boundary); - - if(successfull) { - cut -= gain; - stopping_rule->push_statistics(gain); - - bool accept_equal = random_functions::nextBool(); - if( cut < best_cut || ( cut == best_cut && accept_equal )) { - best_cut = cut; - min_cut_index = number_of_swaps; - if(cut < best_cut) - stopping_rule->reset_statistics(); - } - - from_partitions.push_back(from); - to_partitions.push_back(G.getPartitionIndex(node)); - transpositions.push_back(node); - } else { - number_of_swaps--; //because it wasnt swaps - } - moved_idx[node].index = MOVED; - ASSERT_TRUE(boundary.assert_bnodes_in_boundaries()); - ASSERT_TRUE(boundary.assert_boundaries_are_bnodes()); - - } - - ASSERT_TRUE(boundary.assert_bnodes_in_boundaries()); - ASSERT_TRUE(boundary.assert_boundaries_are_bnodes()); - - //roll backwards - for(number_of_swaps--; number_of_swaps>min_cut_index; number_of_swaps--) { - ASSERT_TRUE(transpositions.size() > 0); - - NodeID node = transpositions.back(); - transpositions.pop_back(); - - PartitionID to = from_partitions.back(); - from_partitions.pop_back(); - to_partitions.pop_back(); - - move_node_back(config, G, node, to, moved_idx, queue, boundary); - } - - - //reconstruct the touched partitions - if(compute_touched_partitions) { - ASSERT_EQ(from_partitions.size(), to_partitions.size()); - for(unsigned i = 0; i < from_partitions.size(); i++) { - touched_blocks[from_partitions[i]] = from_partitions[i]; - touched_blocks[to_partitions[i]] = to_partitions[i]; - } - } - - ASSERT_TRUE(boundary.assert_bnodes_in_boundaries()); - ASSERT_TRUE(boundary.assert_boundaries_are_bnodes()); - - delete queue; - delete stopping_rule; - return initial_cut - best_cut; + PartitionID from = G.getPartitionIndex(node); + bool successfull = move_node(config, G, node, moved_idx, queue, boundary); + + if(successfull) { + cut -= gain; + stopping_rule->push_statistics(gain); + + bool accept_equal = random_functions::nextBool(); + if( cut < best_cut || ( cut == best_cut && accept_equal )) { + best_cut = cut; + min_cut_index = number_of_swaps; + if(cut < best_cut) + stopping_rule->reset_statistics(); + } + + from_partitions.push_back(from); + to_partitions.push_back(G.getPartitionIndex(node)); + transpositions.push_back(node); + } else { + number_of_swaps--; //because it wasnt swaps + } + moved_idx[node].index = MOVED; + ASSERT_TRUE(boundary.assert_bnodes_in_boundaries()); + ASSERT_TRUE(boundary.assert_boundaries_are_bnodes()); + + } + + ASSERT_TRUE(boundary.assert_bnodes_in_boundaries()); + ASSERT_TRUE(boundary.assert_boundaries_are_bnodes()); + + //roll backwards + for(number_of_swaps--; number_of_swaps>min_cut_index; number_of_swaps--) { + ASSERT_TRUE(transpositions.size() > 0); + + NodeID node = transpositions.back(); + transpositions.pop_back(); + + PartitionID to = from_partitions.back(); + from_partitions.pop_back(); + to_partitions.pop_back(); + + move_node_back(config, G, node, to, moved_idx, queue, boundary); + } + + + //reconstruct the touched partitions + if(compute_touched_partitions) { + ASSERT_EQ(from_partitions.size(), to_partitions.size()); + for(unsigned i = 0; i < from_partitions.size(); i++) { + touched_blocks[from_partitions[i]] = from_partitions[i]; + touched_blocks[to_partitions[i]] = to_partitions[i]; + } + } + + ASSERT_TRUE(boundary.assert_bnodes_in_boundaries()); + ASSERT_TRUE(boundary.assert_boundaries_are_bnodes()); + + delete queue; + delete stopping_rule; + return initial_cut - best_cut; } void kway_graph_refinement_core::init_queue_with_boundary(const PartitionConfig & config, @@ -174,50 +174,50 @@ void kway_graph_refinement_core::init_queue_with_boundary(const PartitionConfig std::vector & bnd_nodes, refinement_pq * queue, vertex_moved_hashtable & moved_idx) { - if(config.permutation_during_refinement == PERMUTATION_QUALITY_FAST) { - random_functions::permutate_vector_fast(bnd_nodes, false); - } else if(config.permutation_during_refinement == PERMUTATION_QUALITY_GOOD) { - random_functions::permutate_vector_good(bnd_nodes, false); - } - - for( unsigned int i = 0; i < bnd_nodes.size(); i++) { - NodeID node = bnd_nodes[i]; - - if( moved_idx.find(node) == moved_idx.end() ) { - PartitionID max_gainer; - EdgeWeight ext_degree; - //compute gain - Gain gain = commons->compute_gain(G, node, max_gainer, ext_degree); - queue->insert(node, gain); - moved_idx[node].index = NOT_MOVED; - } - } + if(config.permutation_during_refinement == PERMUTATION_QUALITY_FAST) { + random_functions::permutate_vector_fast(bnd_nodes, false); + } else if(config.permutation_during_refinement == PERMUTATION_QUALITY_GOOD) { + random_functions::permutate_vector_good(bnd_nodes, false); + } + + for( unsigned int i = 0; i < bnd_nodes.size(); i++) { + NodeID node = bnd_nodes[i]; + + if( moved_idx.find(node) == moved_idx.end() ) { + PartitionID max_gainer; + EdgeWeight ext_degree; + //compute gain + Gain gain = commons->compute_gain(G, node, max_gainer, ext_degree); + queue->insert(node, gain); + moved_idx[node].index = NOT_MOVED; + } + } } -void kway_graph_refinement_core::move_node_back(PartitionConfig & config, - graph_access & G, +void kway_graph_refinement_core::move_node_back(PartitionConfig & config, + graph_access & G, NodeID & node, - PartitionID & to, - vertex_moved_hashtable & moved_idx, - refinement_pq * queue, + PartitionID & to, + vertex_moved_hashtable & moved_idx, + refinement_pq * queue, complete_boundary & boundary) { - PartitionID from = G.getPartitionIndex(node); - G.setPartitionIndex(node, to); + PartitionID from = G.getPartitionIndex(node); + G.setPartitionIndex(node, to); - boundary_pair pair; - pair.k = config.k; - pair.lhs = from; - pair.rhs = to; + boundary_pair pair; + pair.k = config.k; + pair.lhs = from; + pair.rhs = to; - //update all boundaries - boundary.postMovedBoundaryNodeUpdates(node, &pair, true, true); + //update all boundaries + boundary.postMovedBoundaryNodeUpdates(node, &pair, true, true); - NodeWeight this_nodes_weight = G.getNodeWeight(node); - boundary.setBlockNoNodes(from, boundary.getBlockNoNodes(from)-1); - boundary.setBlockNoNodes(to, boundary.getBlockNoNodes(to)+1); - boundary.setBlockWeight( from, boundary.getBlockWeight(from)-this_nodes_weight); - boundary.setBlockWeight( to, boundary.getBlockWeight(to)+this_nodes_weight); + NodeWeight this_nodes_weight = G.getNodeWeight(node); + boundary.setBlockNoNodes(from, boundary.getBlockNoNodes(from)-1); + boundary.setBlockNoNodes(to, boundary.getBlockNoNodes(to)+1); + boundary.setBlockWeight( from, boundary.getBlockWeight(from)-this_nodes_weight); + boundary.setBlockWeight( to, boundary.getBlockWeight(to)+this_nodes_weight); +} } - diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_core.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_core.h index 712ceb1b..77b24c0a 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_core.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_core.h @@ -17,65 +17,65 @@ #include "tools/random_functions.h" #include "uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/vertex_moved_hashtable.h" #include "uncoarsening/refinement/refinement.h" - +namespace kahip::modified { class kway_graph_refinement_core { - public: - kway_graph_refinement_core( ); - virtual ~kway_graph_refinement_core(); - - EdgeWeight single_kway_refinement_round(PartitionConfig & config, - graph_access & G, - complete_boundary & boundary, - boundary_starting_nodes & start_nodes, - int step_limit, - vertex_moved_hashtable & moved_idx ); - - EdgeWeight single_kway_refinement_round(PartitionConfig & config, - graph_access & G, - complete_boundary & boundary, - boundary_starting_nodes & start_nodes, - int step_limit, - vertex_moved_hashtable & moved_idx, - std::unordered_map & touched_blocks); - - - private: - EdgeWeight single_kway_refinement_round_internal(PartitionConfig & config, - graph_access & G, - complete_boundary & boundary, - boundary_starting_nodes & start_nodes, - int step_limit, - vertex_moved_hashtable & moved_idx, - bool compute_touched_partitions, - std::unordered_map & touched_blocks); - - - void init_queue_with_boundary(const PartitionConfig & config, - graph_access & G, - std::vector & bnd_nodes, - refinement_pq * queue, - vertex_moved_hashtable & moved_idx); - - inline bool move_node(PartitionConfig & config, - graph_access & G, - NodeID & node, - vertex_moved_hashtable & moved_idx, - refinement_pq * queue, - complete_boundary & boundary); - - inline void move_node_back(PartitionConfig & config, - graph_access & G, - NodeID & node, - PartitionID & to, - vertex_moved_hashtable & moved_idx, - refinement_pq * queue, - complete_boundary & boundary); - - void initialize_partition_moves_array(PartitionConfig & config, - complete_boundary & boundary, - std::vector & partition_move_valid); - - kway_graph_refinement_commons* commons; +public: + kway_graph_refinement_core( ); + virtual ~kway_graph_refinement_core(); + + EdgeWeight single_kway_refinement_round(PartitionConfig & config, + graph_access & G, + complete_boundary & boundary, + boundary_starting_nodes & start_nodes, + int step_limit, + vertex_moved_hashtable & moved_idx ); + + EdgeWeight single_kway_refinement_round(PartitionConfig & config, + graph_access & G, + complete_boundary & boundary, + boundary_starting_nodes & start_nodes, + int step_limit, + vertex_moved_hashtable & moved_idx, + std::unordered_map & touched_blocks); + + +private: + EdgeWeight single_kway_refinement_round_internal(PartitionConfig & config, + graph_access & G, + complete_boundary & boundary, + boundary_starting_nodes & start_nodes, + int step_limit, + vertex_moved_hashtable & moved_idx, + bool compute_touched_partitions, + std::unordered_map & touched_blocks); + + + void init_queue_with_boundary(const PartitionConfig & config, + graph_access & G, + std::vector & bnd_nodes, + refinement_pq * queue, + vertex_moved_hashtable & moved_idx); + + inline bool move_node(PartitionConfig & config, + graph_access & G, + NodeID & node, + vertex_moved_hashtable & moved_idx, + refinement_pq * queue, + complete_boundary & boundary); + + inline void move_node_back(PartitionConfig & config, + graph_access & G, + NodeID & node, + PartitionID & to, + vertex_moved_hashtable & moved_idx, + refinement_pq * queue, + complete_boundary & boundary); + + void initialize_partition_moves_array(PartitionConfig & config, + complete_boundary & boundary, + std::vector & partition_move_valid); + + kway_graph_refinement_commons* commons; }; inline bool kway_graph_refinement_core::move_node(PartitionConfig & config, @@ -143,6 +143,6 @@ inline bool kway_graph_refinement_core::move_node(PartitionConfig & config, return true; } - +} #endif //[> end of include guard: KWAY_GRAPH_REFINEMENT_PVGY97EW <] diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_stop_rule.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_stop_rule.h index 87bfed77..a452c53f 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_stop_rule.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/kway_stop_rule.h @@ -7,7 +7,7 @@ #ifndef KWAY_STOP_RULE_ULPK0ZTF #define KWAY_STOP_RULE_ULPK0ZTF - +namespace kahip::modified { class kway_stop_rule { public: kway_stop_rule(PartitionConfig & config) {}; @@ -90,7 +90,7 @@ inline bool kway_adaptive_stop_rule::search_should_stop(unsigned int min_cut_idx return m_steps*m_expected_gain*m_expected_gain > pconfig->kway_adaptive_limits_alpha * m_expected_variance2 + pconfig->kway_adaptive_limits_beta && (m_steps != 1); } - +} #endif /* end of include guard: KWAY_STOP_RULE_ULPK0ZTF */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/multitry_kway_fm.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/multitry_kway_fm.cpp index 3ffac667..39e8c9ce 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/multitry_kway_fm.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/multitry_kway_fm.cpp @@ -14,7 +14,7 @@ #include "quality_metrics.h" #include "random_functions.h" #include "uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/vertex_moved_hashtable.h" - +namespace kahip::modified { multitry_kway_fm::multitry_kway_fm() { } @@ -26,151 +26,151 @@ int multitry_kway_fm::perform_refinement(PartitionConfig & config, graph_access complete_boundary & boundary, unsigned rounds, bool init_neighbors, unsigned alpha) { - commons = kway_graph_refinement_commons::getInstance(config); - - unsigned tmp_alpha = config.kway_adaptive_limits_alpha; - KWayStopRule tmp_stop = config.kway_stop_rule; - config.kway_adaptive_limits_alpha = alpha; - config.kway_stop_rule = KWAY_ADAPTIVE_STOP_RULE; - - int overall_improvement = 0; - for( unsigned i = 0; i < rounds; i++) { - boundary_starting_nodes start_nodes; - boundary.setup_start_nodes_all(G, start_nodes); - if(start_nodes.size() == 0) { - return 0; - }// nothing to refine - - //now we do something with the start nodes - //convert it into a list - std::vector todolist; - for(unsigned i = 0; i < start_nodes.size(); i++) { - todolist.push_back(start_nodes[i]); - } - - std::unordered_map touched_blocks; - EdgeWeight improvement = start_more_locallized_search(config, G, boundary, - init_neighbors, false, touched_blocks, - todolist); - if( improvement == 0 ) break; - overall_improvement += improvement; + commons = kway_graph_refinement_commons::getInstance(config); - } + unsigned tmp_alpha = config.kway_adaptive_limits_alpha; + KWayStopRule tmp_stop = config.kway_stop_rule; + config.kway_adaptive_limits_alpha = alpha; + config.kway_stop_rule = KWAY_ADAPTIVE_STOP_RULE; - ASSERT_TRUE(overall_improvement >= 0); + int overall_improvement = 0; + for( unsigned i = 0; i < rounds; i++) { + boundary_starting_nodes start_nodes; + boundary.setup_start_nodes_all(G, start_nodes); + if(start_nodes.size() == 0) { + return 0; + }// nothing to refine - config.kway_adaptive_limits_alpha = tmp_alpha; - config.kway_stop_rule = tmp_stop; + //now we do something with the start nodes + //convert it into a list + std::vector todolist; + for(unsigned i = 0; i < start_nodes.size(); i++) { + todolist.push_back(start_nodes[i]); + } + + std::unordered_map touched_blocks; + EdgeWeight improvement = start_more_locallized_search(config, G, boundary, + init_neighbors, false, touched_blocks, + todolist); + if( improvement == 0 ) break; + overall_improvement += improvement; + + } + + ASSERT_TRUE(overall_improvement >= 0); + + config.kway_adaptive_limits_alpha = tmp_alpha; + config.kway_stop_rule = tmp_stop; + + return (int) overall_improvement; - return (int) overall_improvement; - } -int multitry_kway_fm::perform_refinement_around_parts(PartitionConfig & config, graph_access & G, - complete_boundary & boundary, bool init_neighbors, - unsigned alpha, - PartitionID & lhs, PartitionID & rhs, +int multitry_kway_fm::perform_refinement_around_parts(PartitionConfig & config, graph_access & G, + complete_boundary & boundary, bool init_neighbors, + unsigned alpha, + PartitionID & lhs, PartitionID & rhs, std::unordered_map & touched_blocks) { - commons = kway_graph_refinement_commons::getInstance(config); - - unsigned tmp_alpha = config.kway_adaptive_limits_alpha; - KWayStopRule tmp_stop = config.kway_stop_rule; - config.kway_adaptive_limits_alpha = alpha; - config.kway_stop_rule = KWAY_ADAPTIVE_STOP_RULE; - int overall_improvement = 0; - - for( unsigned i = 0; i < config.local_multitry_rounds; i++) { - boundary_starting_nodes start_nodes; - boundary.setup_start_nodes_around_blocks(G, lhs, rhs, start_nodes); - - if(start_nodes.size() == 0) { return 0; }// nothing to refine - - //now we do something with the start nodes - std::vector todolist; - for(unsigned i = 0; i < start_nodes.size(); i++) { - todolist.push_back(start_nodes[i]); - } - - EdgeWeight improvement = start_more_locallized_search(config, G, boundary, - init_neighbors, true, - touched_blocks, todolist); - if( improvement == 0 ) break; - - overall_improvement += improvement; - } - - config.kway_adaptive_limits_alpha = tmp_alpha; - config.kway_stop_rule = tmp_stop; - ASSERT_TRUE(overall_improvement >= 0); - return (int) overall_improvement; + commons = kway_graph_refinement_commons::getInstance(config); + + unsigned tmp_alpha = config.kway_adaptive_limits_alpha; + KWayStopRule tmp_stop = config.kway_stop_rule; + config.kway_adaptive_limits_alpha = alpha; + config.kway_stop_rule = KWAY_ADAPTIVE_STOP_RULE; + int overall_improvement = 0; + + for( unsigned i = 0; i < config.local_multitry_rounds; i++) { + boundary_starting_nodes start_nodes; + boundary.setup_start_nodes_around_blocks(G, lhs, rhs, start_nodes); + + if(start_nodes.size() == 0) { return 0; }// nothing to refine + + //now we do something with the start nodes + std::vector todolist; + for(unsigned i = 0; i < start_nodes.size(); i++) { + todolist.push_back(start_nodes[i]); + } + + EdgeWeight improvement = start_more_locallized_search(config, G, boundary, + init_neighbors, true, + touched_blocks, todolist); + if( improvement == 0 ) break; + + overall_improvement += improvement; + } + + config.kway_adaptive_limits_alpha = tmp_alpha; + config.kway_stop_rule = tmp_stop; + ASSERT_TRUE(overall_improvement >= 0); + return (int) overall_improvement; } -int multitry_kway_fm::start_more_locallized_search(PartitionConfig & config, graph_access & G, - complete_boundary & boundary, bool init_neighbors, - bool compute_touched_blocks, - std::unordered_map & touched_blocks, +int multitry_kway_fm::start_more_locallized_search(PartitionConfig & config, graph_access & G, + complete_boundary & boundary, bool init_neighbors, + bool compute_touched_blocks, + std::unordered_map & touched_blocks, std::vector & todolist) { - random_functions::permutate_vector_good(todolist, false); - commons = kway_graph_refinement_commons::getInstance(config); - - kway_graph_refinement_core refinement_core; - int local_step_limit = 0; - - vertex_moved_hashtable moved_idx; - unsigned idx = todolist.size()-1; - int overall_improvement = 0; - - while(!todolist.empty()) { - int random_idx = random_functions::nextInt(0, idx); - NodeID node = todolist[random_idx]; - - PartitionID maxgainer; - EdgeWeight extdeg = 0; - commons->compute_gain(G, node, maxgainer, extdeg); - - if(moved_idx.find(node) == moved_idx.end() && extdeg > 0) { - boundary_starting_nodes real_start_nodes; - real_start_nodes.push_back(node); - - if(init_neighbors) { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if(moved_idx.find(target) == moved_idx.end()) { - extdeg = 0; - commons->compute_gain(G, target, maxgainer, extdeg); - if(extdeg > 0) { - real_start_nodes.push_back(target); - } - } - } endfor - } - int improvement = 0; - if(compute_touched_blocks) { - improvement = refinement_core.single_kway_refinement_round(config, G, - boundary, real_start_nodes, - local_step_limit, moved_idx, - touched_blocks); - if(improvement < 0) { - std::cout << "buf error improvement < 0" << std::endl; - } - } else { - improvement = refinement_core.single_kway_refinement_round(config, G, - boundary, real_start_nodes, - local_step_limit, moved_idx); - if(improvement < 0) { - std::cout << "buf error improvement < 0" << std::endl; - } - } - - overall_improvement += improvement; - - } - - if(moved_idx.size() > 0.05*G.number_of_nodes()) break; - std::swap(todolist[random_idx], todolist[idx--]); todolist.pop_back(); + random_functions::permutate_vector_good(todolist, false); + commons = kway_graph_refinement_commons::getInstance(config); + + kway_graph_refinement_core refinement_core; + int local_step_limit = 0; + + vertex_moved_hashtable moved_idx; + unsigned idx = todolist.size()-1; + int overall_improvement = 0; + + while(!todolist.empty()) { + int random_idx = random_functions::nextInt(0, idx); + NodeID node = todolist[random_idx]; + + PartitionID maxgainer; + EdgeWeight extdeg = 0; + commons->compute_gain(G, node, maxgainer, extdeg); + + if(moved_idx.find(node) == moved_idx.end() && extdeg > 0) { + boundary_starting_nodes real_start_nodes; + real_start_nodes.push_back(node); + + if(init_neighbors) { + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + if(moved_idx.find(target) == moved_idx.end()) { + extdeg = 0; + commons->compute_gain(G, target, maxgainer, extdeg); + if(extdeg > 0) { + real_start_nodes.push_back(target); + } + } + } endfor +} + int improvement = 0; + if(compute_touched_blocks) { + improvement = refinement_core.single_kway_refinement_round(config, G, + boundary, real_start_nodes, + local_step_limit, moved_idx, + touched_blocks); + if(improvement < 0) { + std::cout << "buf error improvement < 0" << std::endl; + } + } else { + improvement = refinement_core.single_kway_refinement_round(config, G, + boundary, real_start_nodes, + local_step_limit, moved_idx); + if(improvement < 0) { + std::cout << "buf error improvement < 0" << std::endl; } + } - return overall_improvement; -} + overall_improvement += improvement; + + } + + if(moved_idx.size() > 0.05*G.number_of_nodes()) break; + std::swap(todolist[random_idx], todolist[idx--]); todolist.pop_back(); + } + return overall_improvement; +} +} diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/multitry_kway_fm.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/multitry_kway_fm.h index a151cd07..d38e5399 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/multitry_kway_fm.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/kway_graph_refinement/multitry_kway_fm.h @@ -13,34 +13,34 @@ #include "definitions.h" #include "kway_graph_refinement_commons.h" #include "uncoarsening/refinement/refinement.h" - +namespace kahip::modified { class multitry_kway_fm { - public: - multitry_kway_fm( ); - virtual ~multitry_kway_fm(); - - int perform_refinement(PartitionConfig & config, graph_access & G, - complete_boundary & boundary, unsigned rounds, - bool init_neighbors, unsigned alpha); - - int perform_refinement_around_parts(PartitionConfig & config, graph_access & G, - complete_boundary & boundary, bool init_neighbors, - unsigned alpha, - PartitionID & lhs, PartitionID & rhs, - std::unordered_map & touched_blocks); - - - private: - int start_more_locallized_search(PartitionConfig & config, graph_access & G, - complete_boundary & boundary, - bool init_neighbors, - bool compute_touched_blocks, - std::unordered_map & touched_blocks, - std::vector & todolist); - - kway_graph_refinement_commons* commons; +public: + multitry_kway_fm( ); + virtual ~multitry_kway_fm(); + + int perform_refinement(PartitionConfig & config, graph_access & G, + complete_boundary & boundary, unsigned rounds, + bool init_neighbors, unsigned alpha); + + int perform_refinement_around_parts(PartitionConfig & config, graph_access & G, + complete_boundary & boundary, bool init_neighbors, + unsigned alpha, + PartitionID & lhs, PartitionID & rhs, + std::unordered_map & touched_blocks); + + +private: + int start_more_locallized_search(PartitionConfig & config, graph_access & G, + complete_boundary & boundary, + bool init_neighbors, + bool compute_touched_blocks, + std::unordered_map & touched_blocks, + std::vector & todolist); + + kway_graph_refinement_commons* commons; }; - +} #endif /* end of include guard: MULTITRY_KWAYFM_PVGY97EW */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/label_propagation_refinement/label_propagation_refinement.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/label_propagation_refinement/label_propagation_refinement.cpp index 0874c8fd..69a3c9ed 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/label_propagation_refinement/label_propagation_refinement.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/label_propagation_refinement/label_propagation_refinement.cpp @@ -8,7 +8,7 @@ #include "label_propagation_refinement.h" #include "partition/coarsening/clustering/node_ordering.h" #include "tools/random_functions.h" - +namespace kahip::modified { label_propagation_refinement::label_propagation_refinement() { } @@ -20,138 +20,139 @@ label_propagation_refinement::~label_propagation_refinement() { EdgeWeight label_propagation_refinement::perform_refinement(PartitionConfig & partition_config, graph_access & G, complete_boundary & boundary) { - NodeWeight block_upperbound = partition_config.upper_bound_partition; - - // in this case the _matching paramter is not used - // coarse_mappng stores cluster id and the mapping (it is identical) - std::vector hash_map(G.number_of_nodes(),0); - std::vector permutation(G.number_of_nodes()); - std::vector cluster_sizes(partition_config.k, 0); - - node_ordering n_ordering; - n_ordering.order_nodes(partition_config, G, permutation); - - std::queue< NodeID > * Q = new std::queue< NodeID >(); - std::queue< NodeID > * next_Q = new std::queue< NodeID >(); - std::vector * Q_contained = new std::vector(G.number_of_nodes(), false); - std::vector * next_Q_contained = new std::vector (G.number_of_nodes(), false); - forall_nodes(G, node) { - cluster_sizes[G.getPartitionIndex(node)] += G.getNodeWeight(node); - Q->push(permutation[node]); + NodeWeight block_upperbound = partition_config.upper_bound_partition; + + // in this case the _matching paramter is not used + // coarse_mappng stores cluster id and the mapping (it is identical) + std::vector hash_map(G.number_of_nodes(),0); + std::vector permutation(G.number_of_nodes()); + std::vector cluster_sizes(partition_config.k, 0); + + node_ordering n_ordering; + n_ordering.order_nodes(partition_config, G, permutation); + + std::queue< NodeID > * Q = new std::queue< NodeID >(); + std::queue< NodeID > * next_Q = new std::queue< NodeID >(); + std::vector * Q_contained = new std::vector(G.number_of_nodes(), false); + std::vector * next_Q_contained = new std::vector (G.number_of_nodes(), false); + forall_nodes(G, node) { + cluster_sizes[G.getPartitionIndex(node)] += G.getNodeWeight(node); + Q->push(permutation[node]); + } endfor + + for( int j = 0; j < partition_config.label_iterations_refinement; j++) { + unsigned int change_counter = 0; + while( !Q->empty() ) { + NodeID node = Q->front(); + Q->pop(); + (*Q_contained)[node] = false; + + //now move the node to the cluster that is most common in the neighborhood + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + hash_map[G.getPartitionIndex(target)]+=G.getEdgeWeight(e); + //std::cout << "curblock " << G.getPartitionIndex(target) << std::endl; + } endfor + + //second sweep for finding max and resetting array + PartitionID max_block = G.getPartitionIndex(node); + PartitionID my_block = G.getPartitionIndex(node); + + PartitionID max_value = 0; + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + PartitionID cur_block = G.getPartitionIndex(target); + PartitionID cur_value = hash_map[cur_block]; + if((cur_value > max_value || (cur_value == max_value && random_functions::nextBool())) + && (cluster_sizes[cur_block] + G.getNodeWeight(node) < block_upperbound || (cur_block == my_block && cluster_sizes[my_block] <= partition_config.upper_bound_partition))) + //&& (!partition_config.graph_allready_partitioned || G.getPartitionIndex(node) == G.getPartitionIndex(target)) + //&& (!partition_config.combine || G.getSecondPartitionIndex(node) == G.getSecondPartitionIndex(target))) + { + max_value = cur_value; + max_block = cur_block; + } + + hash_map[cur_block] = 0; + } endfor + + cluster_sizes[G.getPartitionIndex(node)] -= G.getNodeWeight(node); + cluster_sizes[max_block] += G.getNodeWeight(node); + bool changed_label = G.getPartitionIndex(node) != max_block; + change_counter += changed_label; + G.setPartitionIndex(node, max_block); + //std::cout << "maxblock " << max_block << std::endl; + + if(changed_label) { + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + if(!(*next_Q_contained)[target]) { + next_Q->push(target); + (*next_Q_contained)[target] = true; + } } endfor +} + } - for( int j = 0; j < partition_config.label_iterations_refinement; j++) { - unsigned int change_counter = 0; - while( !Q->empty() ) { - NodeID node = Q->front(); - Q->pop(); - (*Q_contained)[node] = false; - - //now move the node to the cluster that is most common in the neighborhood - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - hash_map[G.getPartitionIndex(target)]+=G.getEdgeWeight(e); - //std::cout << "curblock " << G.getPartitionIndex(target) << std::endl; - } endfor - - //second sweep for finding max and resetting array - PartitionID max_block = G.getPartitionIndex(node); - PartitionID my_block = G.getPartitionIndex(node); - - PartitionID max_value = 0; - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - PartitionID cur_block = G.getPartitionIndex(target); - PartitionID cur_value = hash_map[cur_block]; - if((cur_value > max_value || (cur_value == max_value && random_functions::nextBool())) - && (cluster_sizes[cur_block] + G.getNodeWeight(node) < block_upperbound || (cur_block == my_block && cluster_sizes[my_block] <= partition_config.upper_bound_partition))) - //&& (!partition_config.graph_allready_partitioned || G.getPartitionIndex(node) == G.getPartitionIndex(target)) - //&& (!partition_config.combine || G.getSecondPartitionIndex(node) == G.getSecondPartitionIndex(target))) - { - max_value = cur_value; - max_block = cur_block; - } - - hash_map[cur_block] = 0; - } endfor - - cluster_sizes[G.getPartitionIndex(node)] -= G.getNodeWeight(node); - cluster_sizes[max_block] += G.getNodeWeight(node); - bool changed_label = G.getPartitionIndex(node) != max_block; - change_counter += changed_label; - G.setPartitionIndex(node, max_block); - //std::cout << "maxblock " << max_block << std::endl; - - if(changed_label) { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if(!(*next_Q_contained)[target]) { - next_Q->push(target); - (*next_Q_contained)[target] = true; - } - } endfor - } - } - - std::swap( Q, next_Q); - std::swap( Q_contained, next_Q_contained); + std::swap( Q, next_Q); + std::swap( Q_contained, next_Q_contained); - } - - - delete Q; - delete next_Q; - delete Q_contained; - delete next_Q_contained; - - - // in this case the _matching paramter is not used - // coarse_mappng stores cluster id and the mapping (it is identical) - //std::vector hash_map(G.number_of_nodes(),0); - //std::vector permutation(G.number_of_nodes()); - //std::vector cluster_sizes(partition_config.k,0); - - //forall_nodes(G, node) { - //cluster_sizes[G.getPartitionIndex(node)] += G.getNodeWeight(node); - //} endfor - - //random_functions::permutate_vector_fast(permutation, true); - //NodeWeight block_upperbound = partition_config.upper_bound_partition; - - //for( int j = 0; j < partition_config.label_iterations; j++) { - //forall_nodes(G, i) { - //NodeID node = permutation[i]; - ////move the node to the cluster that is most common in the neighborhood - - //forall_out_edges(G, e, node) { - //NodeID target = G.getEdgeTarget(e); - //hash_map[G.getPartitionIndex(target)]+=G.getEdgeWeight(e); - //} endfor - - ////second sweep for finding max and resetting array - //PartitionID max_block = G.getPartitionIndex(node); - //PartitionID my_block = G.getPartitionIndex(node); - - //PartitionID max_value = 0; - //forall_out_edges(G, e, node) { - //NodeID target = G.getEdgeTarget(e); - //PartitionID cur_block = G.getPartitionIndex(target); - //PartitionID cur_value = hash_map[cur_block]; - //if((cur_value > max_value || (cur_value == max_value && random_functions::nextBool())) - //&& (cluster_sizes[cur_block] + G.getNodeWeight(node) < block_upperbound || cur_block == my_block)) - //{ - //max_value = cur_value; - //max_block = cur_block; - //} - - //hash_map[cur_block] = 0; - //} endfor - //cluster_sizes[G.getPartitionIndex(node)] -= G.getNodeWeight(node); - //cluster_sizes[max_block] += G.getNodeWeight(node); - //G.setPartitionIndex(node,max_block); - //} endfor - //} - - return 0; + } + + + delete Q; + delete next_Q; + delete Q_contained; + delete next_Q_contained; + + + // in this case the _matching paramter is not used + // coarse_mappng stores cluster id and the mapping (it is identical) + //std::vector hash_map(G.number_of_nodes(),0); + //std::vector permutation(G.number_of_nodes()); + //std::vector cluster_sizes(partition_config.k,0); + + //forall_nodes(G, node) { + //cluster_sizes[G.getPartitionIndex(node)] += G.getNodeWeight(node); + //} endfor + + //random_functions::permutate_vector_fast(permutation, true); + //NodeWeight block_upperbound = partition_config.upper_bound_partition; + + //for( int j = 0; j < partition_config.label_iterations; j++) { + //forall_nodes(G, i) { + //NodeID node = permutation[i]; + ////move the node to the cluster that is most common in the neighborhood + + //forall_out_edges(G, e, node) { + //NodeID target = G.getEdgeTarget(e); + //hash_map[G.getPartitionIndex(target)]+=G.getEdgeWeight(e); + //} endfor + + ////second sweep for finding max and resetting array + //PartitionID max_block = G.getPartitionIndex(node); + //PartitionID my_block = G.getPartitionIndex(node); + + //PartitionID max_value = 0; + //forall_out_edges(G, e, node) { + //NodeID target = G.getEdgeTarget(e); + //PartitionID cur_block = G.getPartitionIndex(target); + //PartitionID cur_value = hash_map[cur_block]; + //if((cur_value > max_value || (cur_value == max_value && random_functions::nextBool())) + //&& (cluster_sizes[cur_block] + G.getNodeWeight(node) < block_upperbound || cur_block == my_block)) + //{ + //max_value = cur_value; + //max_block = cur_block; + //} + + //hash_map[cur_block] = 0; + //} endfor + //cluster_sizes[G.getPartitionIndex(node)] -= G.getNodeWeight(node); + //cluster_sizes[max_block] += G.getNodeWeight(node); + //G.setPartitionIndex(node,max_block); + //} endfor + //} + + return 0; } +} \ No newline at end of file diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/label_propagation_refinement/label_propagation_refinement.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/label_propagation_refinement/label_propagation_refinement.h index 99c46ca1..00c6104c 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/label_propagation_refinement/label_propagation_refinement.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/label_propagation_refinement/label_propagation_refinement.h @@ -11,16 +11,16 @@ #include "definitions.h" #include "../refinement.h" - +namespace kahip::modified { class label_propagation_refinement : public refinement { public: - label_propagation_refinement(); - virtual ~label_propagation_refinement(); + label_propagation_refinement(); + virtual ~label_propagation_refinement(); - virtual EdgeWeight perform_refinement(PartitionConfig & config, - graph_access & G, - complete_boundary & boundary); + virtual EdgeWeight perform_refinement(PartitionConfig & config, + graph_access & G, + complete_boundary & boundary); }; - +} #endif /* end of include guard: LABEL_PROPAGATION_REFINEMENT_R4XW141Y */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/mixed_refinement.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/mixed_refinement.cpp index 990a2ce4..103f5287 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/mixed_refinement.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/mixed_refinement.cpp @@ -10,7 +10,7 @@ #include "kway_graph_refinement/multitry_kway_fm.h" #include "mixed_refinement.h" #include "quotient_graph_refinement/quotient_graph_refinement.h" - +namespace kahip::modified { mixed_refinement::mixed_refinement() { } @@ -20,48 +20,48 @@ mixed_refinement::~mixed_refinement() { } EdgeWeight mixed_refinement::perform_refinement(PartitionConfig & config, graph_access & G, complete_boundary & boundary) { - refinement* refine = new quotient_graph_refinement(); - refinement* kway = new kway_graph_refinement(); - multitry_kway_fm* multitry_kway = new multitry_kway_fm(); - cycle_refinement* cycle_refine = new cycle_refinement(); + refinement* refine = new quotient_graph_refinement(); + refinement* kway = new kway_graph_refinement(); + multitry_kway_fm* multitry_kway = new multitry_kway_fm(); + cycle_refinement* cycle_refine = new cycle_refinement(); - EdgeWeight overall_improvement = 0; - //call refinement - if(config.no_change_convergence) { - bool sth_changed = true; - while(sth_changed) { - EdgeWeight improvement = 0; - if(config.corner_refinement_enabled) { - improvement += kway->perform_refinement(config, G, boundary); - } + EdgeWeight overall_improvement = 0; + //call refinement + if(config.no_change_convergence) { + bool sth_changed = true; + while(sth_changed) { + EdgeWeight improvement = 0; + if(config.corner_refinement_enabled) { + improvement += kway->perform_refinement(config, G, boundary); + } - if(!config.quotient_graph_refinement_disabled) { - improvement += refine->perform_refinement(config, G, boundary); - } + if(!config.quotient_graph_refinement_disabled) { + improvement += refine->perform_refinement(config, G, boundary); + } - overall_improvement += improvement; - sth_changed = improvement != 0; - } + overall_improvement += improvement; + sth_changed = improvement != 0; + } - } else { - if(config.corner_refinement_enabled) { - overall_improvement += kway->perform_refinement(config, G, boundary); - } + } else { + if(config.corner_refinement_enabled) { + overall_improvement += kway->perform_refinement(config, G, boundary); + } - if(!config.quotient_graph_refinement_disabled) { - overall_improvement += refine->perform_refinement(config, G, boundary); - } + if(!config.quotient_graph_refinement_disabled) { + overall_improvement += refine->perform_refinement(config, G, boundary); + } - if(config.kaffpa_perfectly_balanced_refinement) { - overall_improvement += cycle_refine->perform_refinement(config, G, boundary); - } - } + if(config.kaffpa_perfectly_balanced_refinement) { + overall_improvement += cycle_refine->perform_refinement(config, G, boundary); + } + } - delete refine; - delete kway; - delete multitry_kway; - delete cycle_refine; + delete refine; + delete kway; + delete multitry_kway; + delete cycle_refine; - return overall_improvement; + return overall_improvement; +} } - diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/mixed_refinement.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/mixed_refinement.h index e746b513..636da22c 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/mixed_refinement.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/mixed_refinement.h @@ -10,16 +10,16 @@ #include "definitions.h" #include "refinement.h" - +namespace kahip::modified { class mixed_refinement : public refinement { public: - mixed_refinement( ); - virtual ~mixed_refinement(); + mixed_refinement( ); + virtual ~mixed_refinement(); - virtual EdgeWeight perform_refinement(PartitionConfig & config, - graph_access & G, - complete_boundary & boundary); + virtual EdgeWeight perform_refinement(PartitionConfig & config, + graph_access & G, + complete_boundary & boundary); }; - +} #endif /* end of include guard: MIXED_REFINEMENT_XJC6COP3 */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/partition_accept_rule.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/partition_accept_rule.h index 2a0e7894..1dd75ef1 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/partition_accept_rule.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/partition_accept_rule.h @@ -8,51 +8,53 @@ #ifndef PARTITION_ACCEPT_RULE_4RXUS4P9 #define PARTITION_ACCEPT_RULE_4RXUS4P9 +#include + #include "partition_config.h" #include "random_functions.h" - +namespace kahip::modified { class partition_accept_rule { - public: - partition_accept_rule( ) {}; - virtual ~partition_accept_rule() {}; +public: + partition_accept_rule( ) {}; + virtual ~partition_accept_rule() {}; - virtual bool accept_partition(PartitionConfig & config, - const EdgeWeight edge_cut, - const NodeWeight lhs_part_weight, - const NodeWeight rhs_part_weight, - const PartitionID lhs, - const PartitionID rhs, - bool & rebalance ) = 0; + virtual bool accept_partition(PartitionConfig & config, + const EdgeWeight edge_cut, + const NodeWeight lhs_part_weight, + const NodeWeight rhs_part_weight, + const PartitionID lhs, + const PartitionID rhs, + bool & rebalance ) = 0; }; class normal_partition_accept_rule : public partition_accept_rule { - public: - normal_partition_accept_rule(PartitionConfig & config, - const EdgeWeight initial_cut, - const NodeWeight initial_lhs_part_weight, - const NodeWeight initial_rhs_part_weight); - virtual ~normal_partition_accept_rule() {}; - - bool accept_partition(PartitionConfig & config, - const EdgeWeight edge_cut, - const NodeWeight lhs_part_weight, - const NodeWeight rhs_part_weight, - const PartitionID lhs, - const PartitionID rhs, - - bool & rebalance); - private: - EdgeWeight best_cut; - NodeWeight cur_lhs_part_weight; - NodeWeight cur_rhs_part_weight; - NodeWeight difference; +public: + normal_partition_accept_rule(PartitionConfig & config, + const EdgeWeight initial_cut, + const NodeWeight initial_lhs_part_weight, + const NodeWeight initial_rhs_part_weight); + virtual ~normal_partition_accept_rule() {}; + + bool accept_partition(PartitionConfig & config, + const EdgeWeight edge_cut, + const NodeWeight lhs_part_weight, + const NodeWeight rhs_part_weight, + const PartitionID lhs, + const PartitionID rhs, + + bool & rebalance); +private: + EdgeWeight best_cut; + NodeWeight cur_lhs_part_weight; + NodeWeight cur_rhs_part_weight; + NodeWeight difference; }; -normal_partition_accept_rule::normal_partition_accept_rule(PartitionConfig & config, - const EdgeWeight initial_cut, - const NodeWeight initial_lhs_part_weight, +inline normal_partition_accept_rule::normal_partition_accept_rule(PartitionConfig & config, + const EdgeWeight initial_cut, + const NodeWeight initial_lhs_part_weight, const NodeWeight initial_rhs_part_weight) { best_cut = initial_cut; @@ -61,93 +63,99 @@ normal_partition_accept_rule::normal_partition_accept_rule(PartitionConfig & con difference = abs((int)cur_lhs_part_weight - (int)cur_rhs_part_weight); } -bool normal_partition_accept_rule::accept_partition(PartitionConfig & config, - const EdgeWeight edge_cut, - const NodeWeight lhs_part_weight, +inline bool normal_partition_accept_rule::accept_partition(PartitionConfig & config, + const EdgeWeight edge_cut, + const NodeWeight lhs_part_weight, const NodeWeight rhs_part_weight, - const PartitionID lhs, - const PartitionID rhs, + const PartitionID lhs, + const PartitionID rhs, bool & rebalance) { NodeWeight cur_diff = abs((int)lhs_part_weight - (int)rhs_part_weight); bool better_cut_within_balance = edge_cut < best_cut; if(config.softrebalance) { - better_cut_within_balance = edge_cut <= best_cut; + better_cut_within_balance = edge_cut <= best_cut; } - better_cut_within_balance = better_cut_within_balance && - lhs_part_weight < config.upper_bound_partition - && rhs_part_weight < config.upper_bound_partition; + better_cut_within_balance = better_cut_within_balance && + lhs_part_weight < config.upper_bound_partition + && rhs_part_weight < config.upper_bound_partition; - if( (better_cut_within_balance - || (cur_diff < difference && edge_cut == best_cut)) + if( (better_cut_within_balance + || (cur_diff < difference && edge_cut == best_cut)) && lhs_part_weight > 0 && rhs_part_weight > 0 ) { best_cut = edge_cut; difference = cur_diff; rebalance = false; - return true; - - } else if(rebalance) { - if(cur_diff < difference - || (cur_diff <= difference && edge_cut < best_cut)) { - best_cut = edge_cut; - difference = cur_diff; - return true; - } - } - return false; + return true; + + } else if(rebalance) { + if(cur_diff < difference + || (cur_diff <= difference && edge_cut < best_cut)) { + best_cut = edge_cut; + difference = cur_diff; + return true; + } + } + return false; } class ip_partition_accept_rule : public partition_accept_rule { - public: - ip_partition_accept_rule(PartitionConfig & config, - const EdgeWeight initial_cut, - const NodeWeight initial_lhs_part_weight, - const NodeWeight initial_rhs_part_weight, - const PartitionID lhs, - const PartitionID rhs); - virtual ~ip_partition_accept_rule() {}; - - bool accept_partition(PartitionConfig & config, - const EdgeWeight edge_cut, - const NodeWeight lhs_part_weight, - const NodeWeight rhs_part_weight, - const PartitionID lhs, - const PartitionID rhs, - bool & rebalance); - private: - EdgeWeight best_cut; - int cur_lhs_overload; - int cur_rhs_overload; +public: + ip_partition_accept_rule(PartitionConfig & config, + const EdgeWeight initial_cut, + const NodeWeight initial_lhs_part_weight, + const NodeWeight initial_rhs_part_weight, + const PartitionID lhs, + const PartitionID rhs); + virtual ~ip_partition_accept_rule() {}; + + bool accept_partition(PartitionConfig & config, + const EdgeWeight edge_cut, + const NodeWeight lhs_part_weight, + const NodeWeight rhs_part_weight, + const PartitionID lhs, + const PartitionID rhs, + bool & rebalance); +private: + EdgeWeight best_cut; + int cur_lhs_overload; + int cur_rhs_overload; }; -ip_partition_accept_rule::ip_partition_accept_rule(PartitionConfig & config, - const EdgeWeight initial_cut, - const NodeWeight initial_lhs_part_weight, +inline ip_partition_accept_rule::ip_partition_accept_rule(PartitionConfig & config, + const EdgeWeight initial_cut, + const NodeWeight initial_lhs_part_weight, const NodeWeight initial_rhs_part_weight, - const PartitionID lhs, + const PartitionID lhs, const PartitionID rhs) { + if (lhs >= config.target_weights.size() || + rhs >= config.target_weights.size()) { + throw std::invalid_argument( + "initial bipartition refinement requires one target weight per block"); + } + best_cut = initial_cut; cur_lhs_overload = std::max( (int)initial_lhs_part_weight - config.target_weights[lhs],0); cur_rhs_overload = std::max( (int)initial_rhs_part_weight - config.target_weights[rhs],0); } -bool ip_partition_accept_rule::accept_partition(PartitionConfig & config, - const EdgeWeight edge_cut, - const NodeWeight lhs_part_weight, +inline bool ip_partition_accept_rule::accept_partition(PartitionConfig & config, + const EdgeWeight edge_cut, + const NodeWeight lhs_part_weight, const NodeWeight rhs_part_weight, - const PartitionID lhs, - const PartitionID rhs, + const PartitionID lhs, + const PartitionID rhs, bool & rebalance) { bool better_cut_within_balance = edge_cut <= best_cut; int act_lhs_overload = std::max( (int)lhs_part_weight - config.target_weights[lhs],0); int act_rhs_overload = std::max( (int)rhs_part_weight - config.target_weights[rhs],0); - better_cut_within_balance = better_cut_within_balance && - act_lhs_overload == 0 && act_rhs_overload == 0; + better_cut_within_balance = better_cut_within_balance && + act_lhs_overload == 0 && act_rhs_overload == 0; if( act_rhs_overload == 0 && act_lhs_overload == 0 ) config.rebalance = false; @@ -159,20 +167,20 @@ bool ip_partition_accept_rule::accept_partition(PartitionConfig & config, cur_lhs_overload = act_lhs_overload; cur_rhs_overload = act_rhs_overload; return true; - } + } } else { - if( (better_cut_within_balance - || (act_rhs_overload + act_lhs_overload < cur_lhs_overload + cur_rhs_overload && edge_cut == best_cut)) + if( (better_cut_within_balance + || (act_rhs_overload + act_lhs_overload < cur_lhs_overload + cur_rhs_overload && edge_cut == best_cut)) && lhs_part_weight > 0 && rhs_part_weight > 0 ) { best_cut = edge_cut; cur_lhs_overload = act_lhs_overload; cur_rhs_overload = act_rhs_overload; - return true; + return true; - } + } } return false; } - +} #endif /* end of include guard: PARTITION_ACCEPT_RULE_4RXUS4P9 */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/queue_selection_strategie.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/queue_selection_strategie.h index e1634a57..8f48970e 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/queue_selection_strategie.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/queue_selection_strategie.h @@ -8,130 +8,170 @@ #ifndef QUEUESELECTIONSTRATEGIE_H_ #define QUEUESELECTIONSTRATEGIE_H_ - +namespace kahip::modified { class queue_selection_strategy { - public: - queue_selection_strategy(PartitionConfig & config) : m_config ( config ) {}; - virtual ~queue_selection_strategy() {}; - virtual void selectQueue(int lhs_part_weight, int rhs_part_weight, - PartitionID lhs, PartitionID rhs, - PartitionID & from, PartitionID & to, - refinement_pq * lhs_queue, refinement_pq * rhs_queue, - refinement_pq** from_queue, refinement_pq** to_queue) = 0; - protected: - PartitionConfig m_config; +public: + queue_selection_strategy(PartitionConfig & config) : m_config ( config ) {}; + virtual ~queue_selection_strategy() {}; + virtual void selectQueue(int lhs_part_weight, int rhs_part_weight, + PartitionID lhs, PartitionID rhs, + PartitionID & from, PartitionID & to, + refinement_pq * lhs_queue, refinement_pq * rhs_queue, + refinement_pq** from_queue, refinement_pq** to_queue) = 0; +protected: + PartitionConfig m_config; }; class queue_selection_diffusion : public queue_selection_strategy { - public: - queue_selection_diffusion(PartitionConfig & config) : queue_selection_strategy(config) {}; - inline void selectQueue(int lhs_part_weight, int rhs_part_weight, - PartitionID lhs, PartitionID rhs, - PartitionID & from, PartitionID & to, - refinement_pq * lhs_queue, refinement_pq * rhs_queue, - refinement_pq** from_queue, refinement_pq** to_queue ) { - if (lhs_part_weight > rhs_part_weight) { - *from_queue = lhs_queue; - *to_queue = rhs_queue; - from = lhs; - to = rhs; - } else { - *from_queue = rhs_queue; - *to_queue = lhs_queue; - from = rhs; - to = lhs; - } +public: + queue_selection_diffusion(PartitionConfig & config) : queue_selection_strategy(config) {}; + inline void selectQueue(int lhs_part_weight, int rhs_part_weight, + PartitionID lhs, PartitionID rhs, + PartitionID & from, PartitionID & to, + refinement_pq * lhs_queue, refinement_pq * rhs_queue, + refinement_pq** from_queue, refinement_pq** to_queue ) { + if (lhs_part_weight > rhs_part_weight) { + *from_queue = lhs_queue; + *to_queue = rhs_queue; + from = lhs; + to = rhs; + } else { + *from_queue = rhs_queue; + *to_queue = lhs_queue; + from = rhs; + to = lhs; } + } }; class queue_selection_topgain : public queue_selection_strategy { - public: - queue_selection_topgain(PartitionConfig & config) : queue_selection_strategy(config) {}; - inline void selectQueue(int lhs_part_weight, int rhs_part_weight, - PartitionID lhs, PartitionID rhs, - PartitionID & from, PartitionID & to, - refinement_pq * lhs_queue, refinement_pq * rhs_queue, - refinement_pq** from_queue, refinement_pq** to_queue ){ - - if( lhs_queue->empty() ) { - *from_queue = rhs_queue; - *to_queue = lhs_queue; - from = rhs; - to = lhs; - return; - } - if( rhs_queue->empty() ) { - *from_queue = lhs_queue; - *to_queue = rhs_queue; - from = lhs; - to = rhs; - return; - } - - Gain lhsGain = lhs_queue->maxValue(); - Gain rhsGain = rhs_queue->maxValue(); +public: + queue_selection_topgain(PartitionConfig & config) : queue_selection_strategy(config) {}; + inline void selectQueue(int lhs_part_weight, int rhs_part_weight, + PartitionID lhs, PartitionID rhs, + PartitionID & from, PartitionID & to, + refinement_pq * lhs_queue, refinement_pq * rhs_queue, + refinement_pq** from_queue, refinement_pq** to_queue ){ + + if( lhs_queue->empty() ) { + *from_queue = rhs_queue; + *to_queue = lhs_queue; + from = rhs; + to = lhs; + return; + } + if( rhs_queue->empty() ) { + *from_queue = lhs_queue; + *to_queue = rhs_queue; + from = lhs; + to = rhs; + return; + } - if(lhsGain > rhsGain){ - *from_queue = lhs_queue; - *to_queue = rhs_queue; - from = lhs; - to = rhs; - } else { - *from_queue = rhs_queue; - *to_queue = lhs_queue; - from = rhs; - to = lhs; - } + Gain lhsGain = lhs_queue->maxValue(); + Gain rhsGain = rhs_queue->maxValue(); + + if(lhsGain > rhsGain){ + *from_queue = lhs_queue; + *to_queue = rhs_queue; + from = lhs; + to = rhs; + } else { + *from_queue = rhs_queue; + *to_queue = lhs_queue; + from = rhs; + to = lhs; } + } }; class queue_selection_topgain_diffusion : public queue_selection_strategy { - public: - queue_selection_topgain_diffusion(PartitionConfig & config) : queue_selection_strategy(config) { - qdiff = new queue_selection_diffusion(m_config); - }; - - ~queue_selection_topgain_diffusion() { - delete qdiff; - }; - - inline void selectQueue(int lhs_part_weight, int rhs_part_weight, - PartitionID lhs, PartitionID rhs, - PartitionID & from, PartitionID & to, - refinement_pq * lhs_queue, refinement_pq * rhs_queue, - refinement_pq** from_queue, refinement_pq** to_queue ) { - - if( lhs_queue->empty() ) { - *from_queue = rhs_queue; - *to_queue = lhs_queue; - from = rhs; - to = lhs; - return; - } - if( rhs_queue->empty() ) { - *from_queue = lhs_queue; - *to_queue = rhs_queue; - from = lhs; - to = rhs; - return; - } +public: + queue_selection_topgain_diffusion(PartitionConfig & config) : queue_selection_strategy(config) { + qdiff = new queue_selection_diffusion(m_config); + }; + + ~queue_selection_topgain_diffusion() { + delete qdiff; + }; + + inline void selectQueue(int lhs_part_weight, int rhs_part_weight, + PartitionID lhs, PartitionID rhs, + PartitionID & from, PartitionID & to, + refinement_pq * lhs_queue, refinement_pq * rhs_queue, + refinement_pq** from_queue, refinement_pq** to_queue ) { + + if( lhs_queue->empty() ) { + *from_queue = rhs_queue; + *to_queue = lhs_queue; + from = rhs; + to = lhs; + return; + } + if( rhs_queue->empty() ) { + *from_queue = lhs_queue; + *to_queue = rhs_queue; + from = lhs; + to = rhs; + return; + } - Gain lhsGain = lhs_queue->maxValue(); - Gain rhsGain = rhs_queue->maxValue(); + Gain lhsGain = lhs_queue->maxValue(); + Gain rhsGain = rhs_queue->maxValue(); - if (lhsGain == rhsGain) { - qdiff->selectQueue(lhs_part_weight, rhs_part_weight, - lhs, rhs, - from, to, - lhs_queue, rhs_queue, - from_queue, to_queue); - - return; - } - if(lhsGain > rhsGain){ + if (lhsGain == rhsGain) { + qdiff->selectQueue(lhs_part_weight, rhs_part_weight, + lhs, rhs, + from, to, + lhs_queue, rhs_queue, + from_queue, to_queue); + + return; + } + if(lhsGain > rhsGain){ + *from_queue = lhs_queue; + *to_queue = rhs_queue; + from = lhs; + to = rhs; + } else { + *from_queue = rhs_queue; + *to_queue = lhs_queue; + from = rhs; + to = lhs; + } + } +private: + queue_selection_strategy* qdiff; +}; + +class queue_selection_diffusion_block_targets : public queue_selection_strategy { +public: + queue_selection_diffusion_block_targets(PartitionConfig & config) : queue_selection_strategy(config) { + qdiff = new queue_selection_topgain_diffusion(config); + }; + + virtual ~queue_selection_diffusion_block_targets() { + delete qdiff; + } + + inline void selectQueue(int lhs_part_weight, int rhs_part_weight, + PartitionID lhs, PartitionID rhs, + PartitionID & from, PartitionID & to, + refinement_pq * lhs_queue, refinement_pq * rhs_queue, + refinement_pq** from_queue, refinement_pq** to_queue ) { + int lhs_overload = std::max( lhs_part_weight - m_config.target_weights[0],0); + int rhs_overload = std::max( rhs_part_weight - m_config.target_weights[1],0); + if( lhs_overload == 0 && rhs_overload == 0) { + qdiff->selectQueue(lhs_part_weight, rhs_part_weight, + lhs, rhs, + from, to, + lhs_queue, rhs_queue, + from_queue, to_queue); + } else { + if (lhs_overload > rhs_overload) { *from_queue = lhs_queue; *to_queue = rhs_queue; from = lhs; @@ -143,50 +183,11 @@ class queue_selection_topgain_diffusion : public queue_selection_strategy { to = lhs; } } - private: - queue_selection_strategy* qdiff; -}; -class queue_selection_diffusion_block_targets : public queue_selection_strategy { - public: - queue_selection_diffusion_block_targets(PartitionConfig & config) : queue_selection_strategy(config) { - qdiff = new queue_selection_topgain_diffusion(config); - }; - - virtual ~queue_selection_diffusion_block_targets() { - delete qdiff; - } - - inline void selectQueue(int lhs_part_weight, int rhs_part_weight, - PartitionID lhs, PartitionID rhs, - PartitionID & from, PartitionID & to, - refinement_pq * lhs_queue, refinement_pq * rhs_queue, - refinement_pq** from_queue, refinement_pq** to_queue ) { - int lhs_overload = std::max( lhs_part_weight - m_config.target_weights[0],0); - int rhs_overload = std::max( rhs_part_weight - m_config.target_weights[1],0); - if( lhs_overload == 0 && rhs_overload == 0) { - qdiff->selectQueue(lhs_part_weight, rhs_part_weight, - lhs, rhs, - from, to, - lhs_queue, rhs_queue, - from_queue, to_queue); - } else { - if (lhs_overload > rhs_overload) { - *from_queue = lhs_queue; - *to_queue = rhs_queue; - from = lhs; - to = rhs; - } else { - *from_queue = rhs_queue; - *to_queue = lhs_queue; - from = rhs; - to = lhs; - } - } - - } + } - private: - queue_selection_strategy* qdiff; +private: + queue_selection_strategy* qdiff; }; +} #endif diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/search_stop_rule.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/search_stop_rule.h index d0c6ac81..1b459f4a 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/search_stop_rule.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/search_stop_rule.h @@ -7,25 +7,25 @@ #ifndef SEARCH_STOP_RULE_R20GH6IN #define SEARCH_STOP_RULE_R20GH6IN - +namespace kahip::modified { class stop_rule { - public: - stop_rule( ) {}; - virtual ~stop_rule() {}; +public: + stop_rule( ) {}; + virtual ~stop_rule() {}; - virtual bool search_should_stop(unsigned int min_cut_idx, - unsigned int cur_idx, - unsigned int search_limit) = 0; + virtual bool search_should_stop(unsigned int min_cut_idx, + unsigned int cur_idx, + unsigned int search_limit) = 0; }; class easy_stop_rule : public stop_rule { - public: - easy_stop_rule( ) {}; - virtual ~easy_stop_rule() {}; +public: + easy_stop_rule( ) {}; + virtual ~easy_stop_rule() {}; - bool search_should_stop(unsigned int min_cut_idx, - unsigned int cur_idx, - unsigned int search_limit); + bool search_should_stop(unsigned int min_cut_idx, + unsigned int cur_idx, + unsigned int search_limit); }; inline bool easy_stop_rule::search_should_stop(unsigned min_cut_idx, @@ -33,5 +33,5 @@ inline bool easy_stop_rule::search_should_stop(unsigned min_cut_idx, unsigned int search_limit) { return cur_idx - min_cut_idx > search_limit; } - +} #endif /* end of include guard: SEARCH_STOP_RULE_R20GH6IN */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/two_way_fm.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/two_way_fm.cpp index cc548029..2b17d6a2 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/two_way_fm.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/two_way_fm.cpp @@ -15,7 +15,7 @@ #include "tools/quality_metrics.h" #include "two_way_fm.h" #include "uncoarsening/refinement/quotient_graph_refinement/partial_boundary.h" - +namespace kahip::modified { two_way_fm::two_way_fm() { } @@ -35,335 +35,335 @@ EdgeWeight two_way_fm::perform_refinement(PartitionConfig & cfg, EdgeWeight & cut, bool & something_changed) { - PartitionConfig config = cfg;//copy it since we make changes on that - if(lhs_start_nodes.size() == 0 or rhs_start_nodes.size() == 0) return 0; // nothing to refine - - quality_metrics qm; - ASSERT_NEQ(pair->lhs, pair->rhs); - ASSERT_TRUE(assert_directed_boundary_condition(G, boundary, pair->lhs, pair->rhs)); - ASSERT_EQ( cut, qm.edge_cut(G, pair->lhs, pair->rhs)); - - refinement_pq* lhs_queue = NULL; - refinement_pq* rhs_queue = NULL; - if(config.use_bucket_queues) { - EdgeWeight max_degree = G.getMaxDegree(); - lhs_queue = new bucket_pq(max_degree); - rhs_queue = new bucket_pq(max_degree); - } else { - lhs_queue = new maxNodeHeap(); - rhs_queue = new maxNodeHeap(); + PartitionConfig config = cfg;//copy it since we make changes on that + if(lhs_start_nodes.size() == 0 or rhs_start_nodes.size() == 0) return 0; // nothing to refine + + quality_metrics qm; + ASSERT_NEQ(pair->lhs, pair->rhs); + ASSERT_TRUE(assert_directed_boundary_condition(G, boundary, pair->lhs, pair->rhs)); + ASSERT_EQ( cut, qm.edge_cut(G, pair->lhs, pair->rhs)); + + refinement_pq* lhs_queue = NULL; + refinement_pq* rhs_queue = NULL; + if(config.use_bucket_queues) { + EdgeWeight max_degree = G.getMaxDegree(); + lhs_queue = new bucket_pq(max_degree); + rhs_queue = new bucket_pq(max_degree); + } else { + lhs_queue = new maxNodeHeap(); + rhs_queue = new maxNodeHeap(); + } + + init_queue_with_boundary(config, G, lhs_start_nodes, lhs_queue, pair->lhs, pair->rhs); + init_queue_with_boundary(config, G, rhs_start_nodes, rhs_queue, pair->rhs, pair->lhs); + + queue_selection_strategy* topgain_queue_select = new queue_selection_topgain(config); + queue_selection_strategy* diffusion_queue_select = new queue_selection_diffusion(config); + queue_selection_strategy* diffusion_queue_select_block_target = new queue_selection_diffusion_block_targets(config); + + vertex_moved_hashtable moved_idx; + + std::vector transpositions; + + EdgeWeight inital_cut = cut; + int max_number_of_swaps = (int)(boundary.getBlockNoNodes(pair->lhs) + boundary.getBlockNoNodes(pair->rhs)); + int step_limit = (int)((config.fm_search_limit/100.0)*max_number_of_swaps); + step_limit = std::max(step_limit, 15); + int min_cut_index = -1; + + refinement_pq* from_queue = 0; + refinement_pq* to_queue = 0; + + PartitionID from = 0; + PartitionID to = 0; + + NodeWeight * from_part_weight = 0; + NodeWeight * to_part_weight = 0; + + stop_rule* st_rule = new easy_stop_rule(); + partition_accept_rule* accept_partition = NULL; + if(config.initial_bipartitioning) { + accept_partition = new ip_partition_accept_rule(config, cut,lhs_part_weight, rhs_part_weight, pair->lhs, pair->rhs); + } else { + accept_partition = new normal_partition_accept_rule(config, cut,lhs_part_weight, rhs_part_weight); + } + queue_selection_strategy* q_select; + + if(config.softrebalance || config.rebalance || config.initial_bipartitioning) { + if(config.initial_bipartitioning) { + q_select = diffusion_queue_select_block_target; + } else { + q_select = diffusion_queue_select; + } + } else { + q_select = topgain_queue_select; + } + + //roll forwards + EdgeWeight best_cut = cut; + int number_of_swaps = 0; + for(number_of_swaps = 0; number_of_swaps < max_number_of_swaps; number_of_swaps++) { + if(st_rule->search_should_stop(min_cut_index, number_of_swaps, step_limit)) break; + + if(lhs_queue->empty() && rhs_queue->empty()) { + break; + } + + q_select->selectQueue(lhs_part_weight, rhs_part_weight, + pair->lhs, pair->rhs, + from,to, + lhs_queue, rhs_queue, + &from_queue, &to_queue); + + if(!from_queue->empty()) { + Gain gain = from_queue->maxValue(); + NodeID node = from_queue->deleteMax(); + + ASSERT_TRUE(moved_idx[node].index == NOT_MOVED); + + boundary.setBlockNoNodes(from, boundary.getBlockNoNodes(from)-1); + boundary.setBlockNoNodes(to, boundary.getBlockNoNodes(to)+1); + + if(from == pair->lhs) { + from_part_weight = &lhs_part_weight; + to_part_weight = &rhs_part_weight; + } else { + from_part_weight = &rhs_part_weight; + to_part_weight = &lhs_part_weight; + } + + move_node(config, G, node, moved_idx, + from_queue, to_queue, + from, to, + pair, + from_part_weight, to_part_weight, + boundary); + + cut -= gain; + + if( accept_partition->accept_partition(config, cut, lhs_part_weight, rhs_part_weight, pair->lhs, pair->rhs, config.rebalance)) { + ASSERT_TRUE( cut <= best_cut || config.rebalance); + if( cut < best_cut ) { + something_changed = true; } - - init_queue_with_boundary(config, G, lhs_start_nodes, lhs_queue, pair->lhs, pair->rhs); - init_queue_with_boundary(config, G, rhs_start_nodes, rhs_queue, pair->rhs, pair->lhs); - - queue_selection_strategy* topgain_queue_select = new queue_selection_topgain(config); - queue_selection_strategy* diffusion_queue_select = new queue_selection_diffusion(config); - queue_selection_strategy* diffusion_queue_select_block_target = new queue_selection_diffusion_block_targets(config); - - vertex_moved_hashtable moved_idx; - - std::vector transpositions; - - EdgeWeight inital_cut = cut; - int max_number_of_swaps = (int)(boundary.getBlockNoNodes(pair->lhs) + boundary.getBlockNoNodes(pair->rhs)); - int step_limit = (int)((config.fm_search_limit/100.0)*max_number_of_swaps); - step_limit = std::max(step_limit, 15); - int min_cut_index = -1; - - refinement_pq* from_queue = 0; - refinement_pq* to_queue = 0; - - PartitionID from = 0; - PartitionID to = 0; - - NodeWeight * from_part_weight = 0; - NodeWeight * to_part_weight = 0; - - stop_rule* st_rule = new easy_stop_rule(); - partition_accept_rule* accept_partition = NULL; - if(config.initial_bipartitioning) { - accept_partition = new ip_partition_accept_rule(config, cut,lhs_part_weight, rhs_part_weight, pair->lhs, pair->rhs); - } else { - accept_partition = new normal_partition_accept_rule(config, cut,lhs_part_weight, rhs_part_weight); - } - queue_selection_strategy* q_select; - - if(config.softrebalance || config.rebalance || config.initial_bipartitioning) { - if(config.initial_bipartitioning) { - q_select = diffusion_queue_select_block_target; - } else { - q_select = diffusion_queue_select; - } - } else { - q_select = topgain_queue_select; - } - - //roll forwards - EdgeWeight best_cut = cut; - int number_of_swaps = 0; - for(number_of_swaps = 0; number_of_swaps < max_number_of_swaps; number_of_swaps++) { - if(st_rule->search_should_stop(min_cut_index, number_of_swaps, step_limit)) break; - - if(lhs_queue->empty() && rhs_queue->empty()) { - break; - } - - q_select->selectQueue(lhs_part_weight, rhs_part_weight, - pair->lhs, pair->rhs, - from,to, - lhs_queue, rhs_queue, - &from_queue, &to_queue); - - if(!from_queue->empty()) { - Gain gain = from_queue->maxValue(); - NodeID node = from_queue->deleteMax(); - - ASSERT_TRUE(moved_idx[node].index == NOT_MOVED); - - boundary.setBlockNoNodes(from, boundary.getBlockNoNodes(from)-1); - boundary.setBlockNoNodes(to, boundary.getBlockNoNodes(to)+1); - - if(from == pair->lhs) { - from_part_weight = &lhs_part_weight; - to_part_weight = &rhs_part_weight; - } else { - from_part_weight = &rhs_part_weight; - to_part_weight = &lhs_part_weight; - } - - move_node(config, G, node, moved_idx, - from_queue, to_queue, - from, to, - pair, - from_part_weight, to_part_weight, - boundary); - - cut -= gain; - - if( accept_partition->accept_partition(config, cut, lhs_part_weight, rhs_part_weight, pair->lhs, pair->rhs, config.rebalance)) { - ASSERT_TRUE( cut <= best_cut || config.rebalance); - if( cut < best_cut ) { - something_changed = true; - } - best_cut = cut; - min_cut_index = number_of_swaps; - } - - transpositions.push_back(node); - moved_idx[node].index = MOVED; - } else { - break; - } - - } - - ASSERT_TRUE(assert_directed_boundary_condition(G, boundary, pair->lhs, pair->rhs)); - ASSERT_EQ( cut, qm.edge_cut(G, pair->lhs, pair->rhs)); - - //roll backwards - for(number_of_swaps--; number_of_swaps > min_cut_index; number_of_swaps--) { - ASSERT_TRUE(transpositions.size() > 0); - - NodeID node = transpositions.back(); - transpositions.pop_back(); - - PartitionID nodes_partition = G.getPartitionIndex(node); - - if(nodes_partition == pair->lhs) { - from_queue = lhs_queue; - to_queue = rhs_queue; - from = pair->lhs; - to = pair->rhs; - from_part_weight = &lhs_part_weight; - to_part_weight = &rhs_part_weight; - } else { - from_queue = rhs_queue; - to_queue = lhs_queue; - from = pair->rhs; - to = pair->lhs; - from_part_weight = &rhs_part_weight; - to_part_weight = &lhs_part_weight; - - } - - boundary.setBlockNoNodes(from, boundary.getBlockNoNodes(from)-1); - boundary.setBlockNoNodes(to, boundary.getBlockNoNodes(to)+1); - - move_node_back(config, G, node, moved_idx, - from_queue, to_queue, - from, to, - pair, - from_part_weight, - to_part_weight, - boundary); - } - - //clean up - cut = best_cut; - - boundary.setEdgeCut(pair, best_cut); - boundary.setBlockWeight(pair->lhs, lhs_part_weight); - boundary.setBlockWeight(pair->rhs, rhs_part_weight); - - delete lhs_queue; - delete rhs_queue; - delete topgain_queue_select; - delete diffusion_queue_select; - delete diffusion_queue_select_block_target; - delete st_rule; - delete accept_partition; - - ASSERT_EQ( cut, qm.edge_cut(G, pair->lhs, pair->rhs)); - ASSERT_TRUE(assert_directed_boundary_condition(G, boundary, pair->lhs, pair->rhs)); - ASSERT_TRUE( (int)inital_cut-(int)best_cut >= 0 || cfg.rebalance); - // the computed partition shouldnt have a edge cut which is worse than the initial one - return inital_cut-best_cut; + best_cut = cut; + min_cut_index = number_of_swaps; + } + + transpositions.push_back(node); + moved_idx[node].index = MOVED; + } else { + break; + } + + } + + ASSERT_TRUE(assert_directed_boundary_condition(G, boundary, pair->lhs, pair->rhs)); + ASSERT_EQ( cut, qm.edge_cut(G, pair->lhs, pair->rhs)); + + //roll backwards + for(number_of_swaps--; number_of_swaps > min_cut_index; number_of_swaps--) { + ASSERT_TRUE(transpositions.size() > 0); + + NodeID node = transpositions.back(); + transpositions.pop_back(); + + PartitionID nodes_partition = G.getPartitionIndex(node); + + if(nodes_partition == pair->lhs) { + from_queue = lhs_queue; + to_queue = rhs_queue; + from = pair->lhs; + to = pair->rhs; + from_part_weight = &lhs_part_weight; + to_part_weight = &rhs_part_weight; + } else { + from_queue = rhs_queue; + to_queue = lhs_queue; + from = pair->rhs; + to = pair->lhs; + from_part_weight = &rhs_part_weight; + to_part_weight = &lhs_part_weight; + + } + + boundary.setBlockNoNodes(from, boundary.getBlockNoNodes(from)-1); + boundary.setBlockNoNodes(to, boundary.getBlockNoNodes(to)+1); + + move_node_back(config, G, node, moved_idx, + from_queue, to_queue, + from, to, + pair, + from_part_weight, + to_part_weight, + boundary); + } + + //clean up + cut = best_cut; + + boundary.setEdgeCut(pair, best_cut); + boundary.setBlockWeight(pair->lhs, lhs_part_weight); + boundary.setBlockWeight(pair->rhs, rhs_part_weight); + + delete lhs_queue; + delete rhs_queue; + delete topgain_queue_select; + delete diffusion_queue_select; + delete diffusion_queue_select_block_target; + delete st_rule; + delete accept_partition; + + ASSERT_EQ( cut, qm.edge_cut(G, pair->lhs, pair->rhs)); + ASSERT_TRUE(assert_directed_boundary_condition(G, boundary, pair->lhs, pair->rhs)); + ASSERT_TRUE( (int)inital_cut-(int)best_cut >= 0 || cfg.rebalance); + // the computed partition shouldnt have a edge cut which is worse than the initial one + return inital_cut-best_cut; } -void two_way_fm::move_node(const PartitionConfig & config, +void two_way_fm::move_node(const PartitionConfig & config, graph_access & G, const NodeID & node, vertex_moved_hashtable & moved_idx, refinement_pq * from_queue, refinement_pq * to_queue, - PartitionID from, + PartitionID from, PartitionID to, - boundary_pair * pair, + boundary_pair * pair, NodeWeight * from_part_weight, NodeWeight * to_part_weight, complete_boundary & boundary) { - //move node - G.setPartitionIndex(node, to); - boundary.deleteNode(node, from, pair); - - EdgeWeight int_degree_node = 0; - EdgeWeight ext_degree_node = 0; - bool difficult_update = int_ext_degree(G, node, to, from, int_degree_node, ext_degree_node); - - - if(ext_degree_node > 0) { - boundary.insert(node, to, pair); + //move node + G.setPartitionIndex(node, to); + boundary.deleteNode(node, from, pair); + + EdgeWeight int_degree_node = 0; + EdgeWeight ext_degree_node = 0; + bool difficult_update = int_ext_degree(G, node, to, from, int_degree_node, ext_degree_node); + + + if(ext_degree_node > 0) { + boundary.insert(node, to, pair); + } + + if(difficult_update) + boundary.postMovedBoundaryNodeUpdates(node, pair, true, false); + + + NodeWeight this_nodes_weight = G.getNodeWeight(node); + (*from_part_weight) -= this_nodes_weight; + (*to_part_weight) += this_nodes_weight; + + //update neighbors + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + PartitionID targets_partition = G.getPartitionIndex(target); + + if((targets_partition != from && targets_partition != to)) { + continue; + } + + EdgeWeight int_degree = 0; + EdgeWeight ext_degree = 0; + + PartitionID other_partition = targets_partition == from ? to : from; + int_ext_degree(G, target, targets_partition, other_partition, int_degree, ext_degree); + + refinement_pq * queue_to_update = 0; + if(targets_partition == from) { + queue_to_update = from_queue; + } else { + queue_to_update = to_queue; + } + + Gain gain = ext_degree - int_degree; + if(queue_to_update->contains(target)) { + if(ext_degree == 0) { + queue_to_update->deleteNode(target); + boundary.deleteNode(target, targets_partition, pair); + } else { + queue_to_update->changeKey(target, gain); + } + } else { + if(ext_degree > 0) { + if(moved_idx[target].index == NOT_MOVED) { + queue_to_update->insert(target, gain); } + boundary.insert(target, targets_partition, pair); + } else { + boundary.deleteNode(target, targets_partition, pair); + } + } - if(difficult_update) - boundary.postMovedBoundaryNodeUpdates(node, pair, true, false); - - - NodeWeight this_nodes_weight = G.getNodeWeight(node); - (*from_part_weight) -= this_nodes_weight; - (*to_part_weight) += this_nodes_weight; - - //update neighbors - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - PartitionID targets_partition = G.getPartitionIndex(target); - - if((targets_partition != from && targets_partition != to)) { - continue; - } - - EdgeWeight int_degree = 0; - EdgeWeight ext_degree = 0; - - PartitionID other_partition = targets_partition == from ? to : from; - int_ext_degree(G, target, targets_partition, other_partition, int_degree, ext_degree); - - refinement_pq * queue_to_update = 0; - if(targets_partition == from) { - queue_to_update = from_queue; - } else { - queue_to_update = to_queue; - } - - Gain gain = ext_degree - int_degree; - if(queue_to_update->contains(target)) { - if(ext_degree == 0) { - queue_to_update->deleteNode(target); - boundary.deleteNode(target, targets_partition, pair); - } else { - queue_to_update->changeKey(target, gain); - } - } else { - if(ext_degree > 0) { - if(moved_idx[target].index == NOT_MOVED) { - queue_to_update->insert(target, gain); - } - boundary.insert(target, targets_partition, pair); - } else { - boundary.deleteNode(target, targets_partition, pair); - } - } - - } endfor + } endfor } -void two_way_fm::move_node_back(const PartitionConfig & config, +void two_way_fm::move_node_back(const PartitionConfig & config, graph_access & G, const NodeID & node, vertex_moved_hashtable & moved_idx, refinement_pq * from_queue, refinement_pq * to_queue, - PartitionID from, + PartitionID from, PartitionID to, - boundary_pair * pair, + boundary_pair * pair, NodeWeight * from_part_weight, NodeWeight * to_part_weight, complete_boundary & boundary) { - ASSERT_NEQ(from, to); - ASSERT_EQ(from, G.getPartitionIndex(node)); + ASSERT_NEQ(from, to); + ASSERT_EQ(from, G.getPartitionIndex(node)); - //move node - G.setPartitionIndex(node, to); - boundary.deleteNode(node, from, pair); + //move node + G.setPartitionIndex(node, to); + boundary.deleteNode(node, from, pair); - EdgeWeight int_degree_node = 0; - EdgeWeight ext_degree_node = 0; - bool update_difficult = int_ext_degree(G, node, to, from, int_degree_node, ext_degree_node); + EdgeWeight int_degree_node = 0; + EdgeWeight ext_degree_node = 0; + bool update_difficult = int_ext_degree(G, node, to, from, int_degree_node, ext_degree_node); - if(ext_degree_node > 0) { - boundary.insert(node, to, pair); - } + if(ext_degree_node > 0) { + boundary.insert(node, to, pair); + } - if(update_difficult) { - boundary.postMovedBoundaryNodeUpdates(node, pair, true, false); - } + if(update_difficult) { + boundary.postMovedBoundaryNodeUpdates(node, pair, true, false); + } - NodeWeight this_nodes_weight = G.getNodeWeight(node); - (*from_part_weight) -= this_nodes_weight; - (*to_part_weight) += this_nodes_weight; + NodeWeight this_nodes_weight = G.getNodeWeight(node); + (*from_part_weight) -= this_nodes_weight; + (*to_part_weight) += this_nodes_weight; - //update neighbors - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - PartitionID targets_partition = G.getPartitionIndex(target); + //update neighbors + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + PartitionID targets_partition = G.getPartitionIndex(target); - if((targets_partition != from && targets_partition != to)) { - //at most difficult update nec. - continue; //they dont need to be updated during this refinement - } + if((targets_partition != from && targets_partition != to)) { + //at most difficult update nec. + continue; //they dont need to be updated during this refinement + } - EdgeWeight int_degree = 0; - EdgeWeight ext_degree = 0; + EdgeWeight int_degree = 0; + EdgeWeight ext_degree = 0; - PartitionID other_partition = targets_partition == from ? to : from; - int_ext_degree(G, target, targets_partition, other_partition, int_degree, ext_degree); + PartitionID other_partition = targets_partition == from ? to : from; + int_ext_degree(G, target, targets_partition, other_partition, int_degree, ext_degree); - if(boundary.contains(target, targets_partition, pair)) { - if(ext_degree == 0) { - boundary.deleteNode(target, targets_partition, pair); - } - } else { - if(ext_degree > 0) { - boundary.insert(target, targets_partition, pair); - } - } + if(boundary.contains(target, targets_partition, pair)) { + if(ext_degree == 0) { + boundary.deleteNode(target, targets_partition, pair); + } + } else { + if(ext_degree > 0) { + boundary.insert(target, targets_partition, pair); + } + } - } endfor + } endfor } @@ -371,78 +371,78 @@ void two_way_fm::move_node_back(const PartitionConfig & config, void two_way_fm::init_queue_with_boundary(const PartitionConfig & config, graph_access & G, std::vector & bnd_nodes, - refinement_pq * queue, - PartitionID partition_of_boundary, + refinement_pq * queue, + PartitionID partition_of_boundary, PartitionID other) { - if(config.permutation_during_refinement == PERMUTATION_QUALITY_FAST) { - random_functions::permutate_vector_fast(bnd_nodes, false); - } else if(config.permutation_during_refinement == PERMUTATION_QUALITY_GOOD) { - random_functions::permutate_vector_good(bnd_nodes, false); - } + if(config.permutation_during_refinement == PERMUTATION_QUALITY_FAST) { + random_functions::permutate_vector_fast(bnd_nodes, false); + } else if(config.permutation_during_refinement == PERMUTATION_QUALITY_GOOD) { + random_functions::permutate_vector_good(bnd_nodes, false); + } - for( unsigned int i = 0, end = bnd_nodes.size(); i < end; i++) { - NodeID cur_bnd_node = bnd_nodes[i]; - //compute gain - EdgeWeight int_degree = 0; - EdgeWeight ext_degree = 0; + for( unsigned int i = 0, end = bnd_nodes.size(); i < end; i++) { + NodeID cur_bnd_node = bnd_nodes[i]; + //compute gain + EdgeWeight int_degree = 0; + EdgeWeight ext_degree = 0; - int_ext_degree(G, cur_bnd_node, partition_of_boundary, other, int_degree, ext_degree); + int_ext_degree(G, cur_bnd_node, partition_of_boundary, other, int_degree, ext_degree); - Gain gain = ext_degree - int_degree; - queue->insert(cur_bnd_node, gain); - ASSERT_TRUE(ext_degree > 0); - ASSERT_EQ(partition_of_boundary, G.getPartitionIndex(cur_bnd_node)); - } + Gain gain = ext_degree - int_degree; + queue->insert(cur_bnd_node, gain); + ASSERT_TRUE(ext_degree > 0); + ASSERT_EQ(partition_of_boundary, G.getPartitionIndex(cur_bnd_node)); + } } ////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////// Assertions for this class//////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#ifndef NDEBUG -bool two_way_fm::assert_only_boundary_nodes(graph_access & G, PartialBoundary & lhs_boundary, +#ifndef NDEBUG +bool two_way_fm::assert_only_boundary_nodes(graph_access & G, PartialBoundary & lhs_boundary, PartitionID lhs, PartitionID rhs) { - forall_boundary_nodes(lhs_boundary, cur_bnd_node) { - EdgeWeight int_degree = 0; - EdgeWeight ext_degree = 0; + forall_boundary_nodes(lhs_boundary, cur_bnd_node) { + EdgeWeight int_degree = 0; + EdgeWeight ext_degree = 0; - int_ext_degree(G, cur_bnd_node, lhs, rhs, int_degree, ext_degree); + int_ext_degree(G, cur_bnd_node, lhs, rhs, int_degree, ext_degree); - ASSERT_EQ(G.getPartitionIndex(cur_bnd_node), lhs); - ASSERT_TRUE(ext_degree > 0); - } endfor - return true; + ASSERT_EQ(G.getPartitionIndex(cur_bnd_node), lhs); + ASSERT_TRUE(ext_degree > 0); + } endfor + return true; } -bool two_way_fm::assert_every_boundary_nodes(graph_access & G, PartialBoundary & lhs_boundary, +bool two_way_fm::assert_every_boundary_nodes(graph_access & G, PartialBoundary & lhs_boundary, PartitionID lhs, PartitionID rhs) { - forall_nodes(G, n) { - EdgeWeight int_degree = 0; - EdgeWeight ext_degree = 0; - if(G.getPartitionIndex(n) == lhs) { - int_ext_degree(G, n, lhs, rhs, int_degree, ext_degree); + forall_nodes(G, n) { + EdgeWeight int_degree = 0; + EdgeWeight ext_degree = 0; + if(G.getPartitionIndex(n) == lhs) { + int_ext_degree(G, n, lhs, rhs, int_degree, ext_degree); - if(ext_degree > 0) { - ASSERT_TRUE(lhs_boundary.contains(n)); - } - } - } endfor + if(ext_degree > 0) { + ASSERT_TRUE(lhs_boundary.contains(n)); + } + } + } endfor - return true; + return true; } -bool two_way_fm::assert_directed_boundary_condition(graph_access & G, complete_boundary & boundary, +bool two_way_fm::assert_directed_boundary_condition(graph_access & G, complete_boundary & boundary, PartitionID lhs, PartitionID rhs) { - ASSERT_TRUE(assert_only_boundary_nodes(G, boundary.getDirectedBoundary(lhs, lhs, rhs) , lhs, rhs)); - ASSERT_TRUE(assert_only_boundary_nodes(G, boundary.getDirectedBoundary(rhs, lhs, rhs) , rhs, lhs)); - ASSERT_TRUE(assert_every_boundary_nodes(G, boundary.getDirectedBoundary(lhs, lhs, rhs) , lhs, rhs)); - ASSERT_TRUE(assert_every_boundary_nodes(G, boundary.getDirectedBoundary(rhs, lhs, rhs) , rhs, lhs)); - return true; + ASSERT_TRUE(assert_only_boundary_nodes(G, boundary.getDirectedBoundary(lhs, lhs, rhs) , lhs, rhs)); + ASSERT_TRUE(assert_only_boundary_nodes(G, boundary.getDirectedBoundary(rhs, lhs, rhs) , rhs, lhs)); + ASSERT_TRUE(assert_every_boundary_nodes(G, boundary.getDirectedBoundary(lhs, lhs, rhs) , lhs, rhs)); + ASSERT_TRUE(assert_every_boundary_nodes(G, boundary.getDirectedBoundary(rhs, lhs, rhs) , rhs, lhs)); + return true; } - #endif +} \ No newline at end of file diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/two_way_fm.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/two_way_fm.h index 2d48978f..3775179a 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/two_way_fm.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/two_way_fm.h @@ -18,88 +18,87 @@ #include "uncoarsening/refinement/quotient_graph_refinement/partial_boundary.h" #include "uncoarsening/refinement/quotient_graph_refinement/two_way_refinement.h" #include "vertex_moved_hashtable.h" - - +namespace kahip::modified { class two_way_fm : public two_way_refinement { - public: - two_way_fm( ); - virtual ~two_way_fm(); - EdgeWeight perform_refinement(PartitionConfig & config, - graph_access & G, - complete_boundary & boundary, - std::vector & lhs_pq_start_nodes, - std::vector & rhs_pq_start_nodes, - boundary_pair * refinement_pair, - NodeWeight & lhs_part_weight, - NodeWeight & rhs_part_weight, - EdgeWeight & cut, - bool & something_changed); - - inline bool int_ext_degree(graph_access & G, - const NodeID & node, - const PartitionID lhs, - const PartitionID rhs, - EdgeWeight & int_degree, - EdgeWeight & ext_degree); - - - private: - void init_queue_with_boundary(const PartitionConfig & config, - graph_access & G, - std::vector &bnd_nodes, - refinement_pq * queue, - PartitionID partition_of_boundary, - PartitionID other); - - - void move_node(const PartitionConfig & config, - graph_access & G, - const NodeID & node, - vertex_moved_hashtable & moved_idx, - refinement_pq * from_queue, - refinement_pq * to_queue, - PartitionID from, - PartitionID to, - boundary_pair * pair, - NodeWeight * from_part_weight, - NodeWeight * to_part_weight, - complete_boundary & boundary); - - void move_node_back(const PartitionConfig & config, - graph_access & G, - const NodeID & node, - vertex_moved_hashtable & moved_idx, - refinement_pq * from_queue, - refinement_pq * to_queue, - PartitionID from, - PartitionID to, - boundary_pair * pair, - NodeWeight * from_part_weight, - NodeWeight * to_part_weight, - complete_boundary & boundary); - - - /////////////////////////////////////////////////////////////////////////// - //Assertions - /////////////////////////////////////////////////////////////////////////// +public: + two_way_fm( ); + virtual ~two_way_fm(); + EdgeWeight perform_refinement(PartitionConfig & config, + graph_access & G, + complete_boundary & boundary, + std::vector & lhs_pq_start_nodes, + std::vector & rhs_pq_start_nodes, + boundary_pair * refinement_pair, + NodeWeight & lhs_part_weight, + NodeWeight & rhs_part_weight, + EdgeWeight & cut, + bool & something_changed); + + inline bool int_ext_degree(graph_access & G, + const NodeID & node, + const PartitionID lhs, + const PartitionID rhs, + EdgeWeight & int_degree, + EdgeWeight & ext_degree); + + +private: + void init_queue_with_boundary(const PartitionConfig & config, + graph_access & G, + std::vector &bnd_nodes, + refinement_pq * queue, + PartitionID partition_of_boundary, + PartitionID other); + + + void move_node(const PartitionConfig & config, + graph_access & G, + const NodeID & node, + vertex_moved_hashtable & moved_idx, + refinement_pq * from_queue, + refinement_pq * to_queue, + PartitionID from, + PartitionID to, + boundary_pair * pair, + NodeWeight * from_part_weight, + NodeWeight * to_part_weight, + complete_boundary & boundary); + + void move_node_back(const PartitionConfig & config, + graph_access & G, + const NodeID & node, + vertex_moved_hashtable & moved_idx, + refinement_pq * from_queue, + refinement_pq * to_queue, + PartitionID from, + PartitionID to, + boundary_pair * pair, + NodeWeight * from_part_weight, + NodeWeight * to_part_weight, + complete_boundary & boundary); + + + /////////////////////////////////////////////////////////////////////////// + //Assertions + /////////////////////////////////////////////////////////////////////////// #ifndef NDEBUG - //assert that every node in the lhs boundary has external degree > 0 - bool assert_only_boundary_nodes(graph_access & G, - PartialBoundary & lhs_boundary, - PartitionID lhs, + //assert that every node in the lhs boundary has external degree > 0 + bool assert_only_boundary_nodes(graph_access & G, + PartialBoundary & lhs_boundary, + PartitionID lhs, + PartitionID rhs); + + //assert that every node with ext degree > 0 is lhs boundary + bool assert_every_boundary_nodes(graph_access & G, + PartialBoundary & lhs_boundary, + PartitionID lhs, + PartitionID rhs); + + //check all of the possible compinations of the two assertions above + bool assert_directed_boundary_condition(graph_access & G, + complete_boundary & boundary, + PartitionID lhs, PartitionID rhs); - - //assert that every node with ext degree > 0 is lhs boundary - bool assert_every_boundary_nodes(graph_access & G, - PartialBoundary & lhs_boundary, - PartitionID lhs, - PartitionID rhs); - - //check all of the possible compinations of the two assertions above - bool assert_directed_boundary_condition(graph_access & G, - complete_boundary & boundary, - PartitionID lhs, - PartitionID rhs); #endif }; @@ -135,6 +134,6 @@ inline bool two_way_fm::int_ext_degree( graph_access & G, return update_is_difficult; } - +} #endif /* end of include guard: TWO_WAY_FM_YLYN82Y1 */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/vertex_moved_hashtable.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/vertex_moved_hashtable.h index f42768ca..d399f1fa 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/vertex_moved_hashtable.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/vertex_moved_hashtable.h @@ -12,11 +12,11 @@ #include "definitions.h" #include "limits.h" - +namespace kahip::modified { struct compare_nodes { - bool operator()(const NodeID lhs, const NodeID rhs) const { - return (lhs == rhs); - } + bool operator()(const NodeID lhs, const NodeID rhs) const { + return (lhs == rhs); + } }; @@ -24,18 +24,18 @@ const NodeID NOT_MOVED = std::numeric_limits::max(); const NodeID MOVED = 0; struct moved_index { - NodeID index; - moved_index() { - index = NOT_MOVED; - } + NodeID index; + moved_index() { + index = NOT_MOVED; + } }; struct hash_nodes { - size_t operator()(const NodeID idx) const { - return idx; - } + size_t operator()(const NodeID idx) const { + return idx; + } }; typedef std::unordered_map vertex_moved_hashtable; - +} #endif diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/boundary_lookup.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/boundary_lookup.h index 0a6b385a..5e781d62 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/boundary_lookup.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/boundary_lookup.h @@ -13,7 +13,7 @@ #include "definitions.h" #include "limits.h" #include "partial_boundary.h" - +namespace kahip::modified { struct boundary_pair { PartitionID k; PartitionID lhs; @@ -24,7 +24,7 @@ struct boundary_pair { struct compare_boundary_pair { bool operator()(const boundary_pair pair_a, const boundary_pair pair_b) const { bool eq = (pair_a.lhs == pair_b.lhs && pair_a.rhs == pair_b.rhs); - eq = eq || (pair_a.lhs == pair_b.rhs && pair_a.rhs == pair_b.lhs); + eq = eq || (pair_a.lhs == pair_b.rhs && pair_a.rhs == pair_b.lhs); return eq; } }; @@ -54,23 +54,22 @@ struct data_boundary_pair { }; struct hash_boundary_pair_directed{ - size_t operator()(const boundary_pair pair) const { + size_t operator()(const boundary_pair pair) const { return pair.lhs*pair.k + pair.rhs; - } + } }; struct hash_boundary_pair{ - size_t operator()(const boundary_pair pair) const { - if(pair.lhs < pair.rhs) + size_t operator()(const boundary_pair pair) const { + if(pair.lhs < pair.rhs) return pair.lhs*pair.k + pair.rhs; - else + else return pair.rhs*pair.k + pair.lhs; - } + } }; typedef std::unordered_map block_pairs; - - +} #endif /* end of include guard: BOUNDARY_LOOKUP_2JMSKBSI */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/complete_boundary.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/complete_boundary.cpp index 6221c455..51c40880 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/complete_boundary.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/complete_boundary.cpp @@ -7,101 +7,102 @@ #include "complete_boundary.h" #include "quality_metrics.h" - +namespace kahip::modified { complete_boundary::complete_boundary(graph_access * G) { - m_graph_ref = G; - m_pb_lhs_lazy = 0; - m_pb_rhs_lazy = 0; - m_last_pair = 0; - m_last_key = -1; - m_block_infos.resize(G->get_partition_count()); - delete Q.graphref; - Q.graphref = NULL; + m_graph_ref = G; + m_pb_lhs_lazy = 0; + m_pb_rhs_lazy = 0; + m_last_pair = 0; + m_last_key = -1; + m_block_infos.resize(G->get_partition_count()); + delete Q.graphref; + Q.graphref = NULL; } complete_boundary::~complete_boundary() { } -void complete_boundary::postMovedBoundaryNodeUpdates(NodeID node, boundary_pair * pair, +void complete_boundary::postMovedBoundaryNodeUpdates(NodeID node, boundary_pair * pair, bool update_edge_cuts, bool update_all_boundaries) { - graph_access & G = *m_graph_ref; - PartitionID to = m_graph_ref->getPartitionIndex(node); - PartitionID from = to == pair->lhs ? pair->rhs : pair->lhs; - ASSERT_NEQ(from, to); - - //First delete this node from all incidient partition boudnary and decreas the edgecut (from, target_partition != to) - //then insert it in the right target - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - PartitionID targetPartition = G.getPartitionIndex(target); - - if(update_all_boundaries || targetPartition != to ) { - //delete - boundary_pair delete_bp; - delete_bp.k = m_graph_ref->get_partition_count(); - delete_bp.lhs = from; - delete_bp.rhs = targetPartition; - - EdgeWeight edge_weight = G.getEdgeWeight(e); - if(targetPartition != from) { - deleteNode(node, from, &delete_bp); - - bool target_is_still_incident = false; - //this should only be delete if there is other incident partition - forall_out_edges(G, t_e, target) { - NodeID targets_target = G.getEdgeTarget(t_e); - NodeID targets_target_partition = G.getPartitionIndex(targets_target); - if(targets_target_partition == from) { - //since partition index of node is to it cant be node, and this edge is - //a widness that target can remain in this boundary - target_is_still_incident = true; - break; - } - } endfor + graph_access & G = *m_graph_ref; + PartitionID to = m_graph_ref->getPartitionIndex(node); + PartitionID from = to == pair->lhs ? pair->rhs : pair->lhs; + ASSERT_NEQ(from, to); + + //First delete this node from all incidient partition boudnary and decreas the edgecut (from, target_partition != to) + //then insert it in the right target + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + PartitionID targetPartition = G.getPartitionIndex(target); + + if(update_all_boundaries || targetPartition != to ) { + //delete + boundary_pair delete_bp; + delete_bp.k = m_graph_ref->get_partition_count(); + delete_bp.lhs = from; + delete_bp.rhs = targetPartition; + + EdgeWeight edge_weight = G.getEdgeWeight(e); + if(targetPartition != from) { + deleteNode(node, from, &delete_bp); + + bool target_is_still_incident = false; + //this should only be delete if there is other incident partition + forall_out_edges(G, t_e, target) { + NodeID targets_target = G.getEdgeTarget(t_e); + NodeID targets_target_partition = G.getPartitionIndex(targets_target); + if(targets_target_partition == from) { + //since partition index of node is to it cant be node, and this edge is + //a widness that target can remain in this boundary + target_is_still_incident = true; + break; + } + } endfor - if(!target_is_still_incident) - deleteNode(target, targetPartition, &delete_bp); + if(!target_is_still_incident) + deleteNode(target, targetPartition, &delete_bp); - if(update_edge_cuts) { - m_pairs[delete_bp].edge_cut -= edge_weight; - } - } + if(update_edge_cuts) { + m_pairs[delete_bp].edge_cut -= edge_weight; + } + } - if(targetPartition != to) { - //insert - boundary_pair insert_bp; - insert_bp.k = m_graph_ref->get_partition_count(); - insert_bp.lhs = to; - insert_bp.rhs = targetPartition; + if(targetPartition != to) { + //insert + boundary_pair insert_bp; + insert_bp.k = m_graph_ref->get_partition_count(); + insert_bp.lhs = to; + insert_bp.rhs = targetPartition; - insert(node, to, &insert_bp); - insert(target, targetPartition, &insert_bp); + insert(node, to, &insert_bp); + insert(target, targetPartition, &insert_bp); - if(update_edge_cuts) { - m_pairs[insert_bp].edge_cut += edge_weight; - } - } - } - } endfor -} + if(update_edge_cuts) { + m_pairs[insert_bp].edge_cut += edge_weight; + } + } + } + } endfor +} void complete_boundary::balance_singletons(const PartitionConfig & config, graph_access & G) { - for( unsigned i = 0; i < m_singletons.size(); i++) { - NodeWeight min = m_block_infos[0].block_weight; - PartitionID p = 0; - for( unsigned j = 0; j < m_block_infos.size(); j++) { - if( m_block_infos[j].block_weight < min ) { - min = m_block_infos[j].block_weight; - p = j; - } - } - - NodeID node = m_singletons[i]; - if( m_block_infos[p].block_weight + G.getNodeWeight(node) <= config.upper_bound_partition) { - m_block_infos[G.getPartitionIndex(node)].block_weight -= G.getNodeWeight(node); - m_block_infos[p].block_weight += G.getNodeWeight(node); - G.setPartitionIndex(node, p); - } - } + for( unsigned i = 0; i < m_singletons.size(); i++) { + NodeWeight min = m_block_infos[0].block_weight; + PartitionID p = 0; + for( unsigned j = 0; j < m_block_infos.size(); j++) { + if( m_block_infos[j].block_weight < min ) { + min = m_block_infos[j].block_weight; + p = j; + } + } + + NodeID node = m_singletons[i]; + if( m_block_infos[p].block_weight + G.getNodeWeight(node) <= config.upper_bound_partition) { + m_block_infos[G.getPartitionIndex(node)].block_weight -= G.getNodeWeight(node); + m_block_infos[p].block_weight += G.getNodeWeight(node); + G.setPartitionIndex(node, p); + } + } } +} \ No newline at end of file diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/complete_boundary.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/complete_boundary.h index 4f666b58..ab32304e 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/complete_boundary.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/complete_boundary.h @@ -16,7 +16,7 @@ #include "data_structure/graph_access.h" #include "partial_boundary.h" #include "partition_config.h" - +namespace kahip::modified { struct block_informations { NodeWeight block_weight; NodeID block_no_nodes; @@ -25,77 +25,77 @@ struct block_informations { typedef std::vector QuotientGraphEdges; class complete_boundary { - public: - complete_boundary(graph_access * G ); - virtual ~complete_boundary(); - - void build(); - void build_from_coarser(complete_boundary * coarser_boundary, NodeID coarser_no_nodes, CoarseMapping * cmapping); - - inline void insert(NodeID node, PartitionID insert_node_into, boundary_pair * pair); - inline bool contains(NodeID node, PartitionID partition, boundary_pair * pair); - inline void deleteNode(NodeID node, PartitionID partition, boundary_pair * pair); - void postMovedBoundaryNodeUpdates(NodeID target, boundary_pair * pair, - bool update_edge_cuts, bool update_all_boundaries); - void balance_singletons(const PartitionConfig & config, graph_access & G); - - inline NodeID size(PartitionID partition, boundary_pair * pair); - - inline NodeWeight getBlockWeight(PartitionID partition); - inline NodeWeight getBlockNoNodes(PartitionID partition); - inline EdgeWeight getEdgeCut(boundary_pair * pair); - inline EdgeWeight getEdgeCut(PartitionID lhs, PartitionID rhs); - - inline void setBlockWeight(PartitionID partition, NodeWeight weight); - inline void setBlockNoNodes(PartitionID partition, NodeID no_nodes); - inline void setEdgeCut(boundary_pair * pair, EdgeWeight edge_cut); - - inline void getQuotientGraphEdges(QuotientGraphEdges & qgraph_edges); - inline PartialBoundary& getDirectedBoundary(PartitionID partition, PartitionID lhs, PartitionID rhs); - - inline void setup_start_nodes(graph_access & G, PartitionID partition, - boundary_pair & bp, boundary_starting_nodes & start_nodes); - - inline void setup_start_nodes_around_blocks(graph_access & G, PartitionID & lhs, PartitionID & rhs, - boundary_starting_nodes & start_nodes); - - inline void setup_start_nodes_all(graph_access & G, boundary_starting_nodes & start_nodes); - - inline void get_max_norm(); - inline void getUnderlyingQuotientGraph( graph_access & qgraph ); - inline void getNeighbors(PartitionID & block, std::vector & neighbors); - - private: - //updates lazy values that the access functions need - inline void update_lazy_values(boundary_pair * pair); - - //lazy members to avoid hashtable loop ups - PartialBoundary* m_pb_lhs_lazy; - PartialBoundary* m_pb_rhs_lazy; - PartitionID m_lazy_lhs; - PartitionID m_lazy_rhs; - boundary_pair* m_last_pair; - size_t m_last_key; - hash_boundary_pair m_hbp; - - graph_access * m_graph_ref; - //implicit quotient graph structure - // - block_pairs m_pairs; - std::vector m_block_infos; - - //explicit quotient graph structure / may be outdated! - graph_access Q; - std::vector< NodeID > m_singletons; - - ////////////////////////////////////////////////////////////// - ///////// Data Structure Invariants - ////////////////////////////////////////////////////////////// +public: + complete_boundary(graph_access * G ); + virtual ~complete_boundary(); + + void build(); + void build_from_coarser(complete_boundary * coarser_boundary, NodeID coarser_no_nodes, CoarseMapping * cmapping); + + inline void insert(NodeID node, PartitionID insert_node_into, boundary_pair * pair); + inline bool contains(NodeID node, PartitionID partition, boundary_pair * pair); + inline void deleteNode(NodeID node, PartitionID partition, boundary_pair * pair); + void postMovedBoundaryNodeUpdates(NodeID target, boundary_pair * pair, + bool update_edge_cuts, bool update_all_boundaries); + void balance_singletons(const PartitionConfig & config, graph_access & G); + + inline NodeID size(PartitionID partition, boundary_pair * pair); + + inline NodeWeight getBlockWeight(PartitionID partition); + inline NodeWeight getBlockNoNodes(PartitionID partition); + inline EdgeWeight getEdgeCut(boundary_pair * pair); + inline EdgeWeight getEdgeCut(PartitionID lhs, PartitionID rhs); + + inline void setBlockWeight(PartitionID partition, NodeWeight weight); + inline void setBlockNoNodes(PartitionID partition, NodeID no_nodes); + inline void setEdgeCut(boundary_pair * pair, EdgeWeight edge_cut); + + inline void getQuotientGraphEdges(QuotientGraphEdges & qgraph_edges); + inline PartialBoundary& getDirectedBoundary(PartitionID partition, PartitionID lhs, PartitionID rhs); + + inline void setup_start_nodes(graph_access & G, PartitionID partition, + boundary_pair & bp, boundary_starting_nodes & start_nodes); + + inline void setup_start_nodes_around_blocks(graph_access & G, PartitionID & lhs, PartitionID & rhs, + boundary_starting_nodes & start_nodes); + + inline void setup_start_nodes_all(graph_access & G, boundary_starting_nodes & start_nodes); + + inline void get_max_norm(); + inline void getUnderlyingQuotientGraph( graph_access & qgraph ); + inline void getNeighbors(PartitionID & block, std::vector & neighbors); + +private: + //updates lazy values that the access functions need + inline void update_lazy_values(boundary_pair * pair); + + //lazy members to avoid hashtable loop ups + PartialBoundary* m_pb_lhs_lazy; + PartialBoundary* m_pb_rhs_lazy; + PartitionID m_lazy_lhs; + PartitionID m_lazy_rhs; + boundary_pair* m_last_pair; + size_t m_last_key; + hash_boundary_pair m_hbp; + + graph_access * m_graph_ref; + //implicit quotient graph structure + // + block_pairs m_pairs; + std::vector m_block_infos; + + //explicit quotient graph structure / may be outdated! + graph_access Q; + std::vector< NodeID > m_singletons; + + ////////////////////////////////////////////////////////////// +///////// Data Structure Invariants +////////////////////////////////////////////////////////////// #ifndef NDEBUG - public: - bool assert_bnodes_in_boundaries(); - bool assert_boundaries_are_bnodes(); -#endif +public: + bool assert_bnodes_in_boundaries(); + bool assert_boundaries_are_bnodes(); +#endif }; @@ -128,22 +128,22 @@ inline void complete_boundary::build() { bp.lhs = source_partition; bp.rhs = target_partition; update_lazy_values(&bp); - m_pairs[bp].edge_cut += G.getEdgeWeight(e); + m_pairs[bp].edge_cut += G.getEdgeWeight(e); insert(n, source_partition, &bp); } } endfor } endfor - block_pairs::iterator iter; - for(iter = m_pairs.begin(); iter != m_pairs.end(); iter++ ) { + block_pairs::iterator iter; + for(iter = m_pairs.begin(); iter != m_pairs.end(); iter++ ) { data_boundary_pair& value = iter->second; value.edge_cut /= 2; } } -inline void complete_boundary::build_from_coarser(complete_boundary * coarser_boundary, - NodeID coarser_no_nodes, +inline void complete_boundary::build_from_coarser(complete_boundary * coarser_boundary, + NodeID coarser_no_nodes, CoarseMapping * cmapping) { graph_access & G = *m_graph_ref; @@ -157,15 +157,15 @@ inline void complete_boundary::build_from_coarser(complete_boundary * coarser_bo PartitionID rhs = coarser_qgraph_edges[i].rhs; PartialBoundary& lhs_b = coarser_boundary->getDirectedBoundary(lhs, lhs, rhs); PartialBoundary& rhs_b = coarser_boundary->getDirectedBoundary(rhs, lhs, rhs); - + forall_boundary_nodes(lhs_b, n) { coarse_is_border_node[n] = true; - } endfor - + } endfor + forall_boundary_nodes(rhs_b, n) { coarse_is_border_node[n] = true; } endfor - + } for(PartitionID block = 0; block < G.get_partition_count(); block++) { @@ -183,7 +183,7 @@ inline void complete_boundary::build_from_coarser(complete_boundary * coarser_bo NodeID coarse_node = (*cmapping)[n]; if(!coarse_is_border_node[coarse_node]) continue; - + forall_out_edges(G, e, n) { NodeID targetID = G.getEdgeTarget(e); PartitionID target_partition = G.getPartitionIndex(targetID); @@ -195,7 +195,7 @@ inline void complete_boundary::build_from_coarser(complete_boundary * coarser_bo bp.lhs = source_partition; bp.rhs = target_partition; update_lazy_values(&bp); - m_pairs[bp].edge_cut += G.getEdgeWeight(e); + m_pairs[bp].edge_cut += G.getEdgeWeight(e); insert(n, source_partition, &bp); } } endfor @@ -205,8 +205,8 @@ inline void complete_boundary::build_from_coarser(complete_boundary * coarser_bo setBlockWeight(p, coarser_boundary->getBlockWeight(p)); } - block_pairs::iterator iter; - for(iter = m_pairs.begin(); iter != m_pairs.end(); iter++ ) { + block_pairs::iterator iter; + for(iter = m_pairs.begin(); iter != m_pairs.end(); iter++ ) { data_boundary_pair& value = iter->second; value.edge_cut /= 2; } @@ -214,7 +214,7 @@ inline void complete_boundary::build_from_coarser(complete_boundary * coarser_bo inline void complete_boundary::insert(NodeID node, PartitionID insert_node_into, boundary_pair * pair) { update_lazy_values(pair); - ASSERT_TRUE((m_lazy_lhs == pair->lhs && m_lazy_rhs == pair->rhs) + ASSERT_TRUE((m_lazy_lhs == pair->lhs && m_lazy_rhs == pair->rhs) || (m_lazy_lhs == pair->rhs && m_lazy_rhs == pair->lhs)); if(insert_node_into == m_lazy_lhs) { @@ -223,7 +223,7 @@ inline void complete_boundary::insert(NodeID node, PartitionID insert_node_into, } else { ASSERT_EQ(m_graph_ref->getPartitionIndex(node),m_lazy_rhs); m_pb_rhs_lazy->insert(node); - } + } } inline bool complete_boundary::contains(NodeID node, PartitionID partition, boundary_pair * pair){ @@ -234,7 +234,7 @@ inline bool complete_boundary::contains(NodeID node, PartitionID partition, boun } else { ASSERT_EQ(m_graph_ref->getPartitionIndex(node),m_lazy_rhs); return m_pb_rhs_lazy->contains(node); - } + } } inline void complete_boundary::deleteNode(NodeID node, PartitionID partition, boundary_pair * pair) { @@ -243,7 +243,7 @@ inline void complete_boundary::deleteNode(NodeID node, PartitionID partition, bo m_pb_lhs_lazy->deleteNode(node); } else { m_pb_rhs_lazy->deleteNode(node); - } + } } inline NodeID complete_boundary::size(PartitionID partition, boundary_pair * pair){ @@ -252,7 +252,7 @@ inline NodeID complete_boundary::size(PartitionID partition, boundary_pair * pai return m_pb_lhs_lazy->size(); } else { return m_pb_rhs_lazy->size(); - } + } } inline NodeWeight complete_boundary::getBlockWeight(PartitionID partition){ @@ -292,8 +292,8 @@ inline void complete_boundary::setEdgeCut(boundary_pair * pair, EdgeWeight edge_ inline void complete_boundary::getQuotientGraphEdges(QuotientGraphEdges & qgraph_edges) { //the quotient graph is stored implicitly in the pairs hashtable - block_pairs::iterator iter; - for(iter = m_pairs.begin(); iter != m_pairs.end(); iter++ ) { + block_pairs::iterator iter; + for(iter = m_pairs.begin(); iter != m_pairs.end(); iter++ ) { boundary_pair key = iter->first; qgraph_edges.push_back(key); } @@ -310,16 +310,16 @@ inline PartialBoundary& complete_boundary::getDirectedBoundary(PartitionID parti return *m_pb_lhs_lazy; } else { return *m_pb_rhs_lazy; - } + } } inline void complete_boundary::update_lazy_values(boundary_pair * pair) { ASSERT_NEQ(pair->lhs, pair->rhs); - + boundary_pair & bp = *pair; - size_t key = m_hbp(bp); + size_t key = m_hbp(bp); if(key != m_last_key) { - data_boundary_pair & dbp = m_pairs[*pair]; + data_boundary_pair & dbp = m_pairs[*pair]; if(!dbp.initialized) { m_pairs[*pair].lhs = pair->lhs; m_pairs[*pair].rhs = pair->rhs; @@ -334,9 +334,9 @@ inline void complete_boundary::update_lazy_values(boundary_pair * pair) { m_last_key = key; } } -void complete_boundary::setup_start_nodes(graph_access & G, - PartitionID partition, - boundary_pair & bp, +void complete_boundary::setup_start_nodes(graph_access & G, + PartitionID partition, + boundary_pair & bp, boundary_starting_nodes & start_nodes) { start_nodes.resize(size(partition, &bp)); @@ -353,65 +353,65 @@ void complete_boundary::setup_start_nodes(graph_access & G, } inline void complete_boundary::get_max_norm() { - QuotientGraphEdges qgraph_edges; - getQuotientGraphEdges(qgraph_edges); - double max = 0; - for( unsigned i = 0; i < qgraph_edges.size(); i++) { - boundary_pair & pair = qgraph_edges[i]; - - if( m_pairs[pair].edge_cut > max ) { - max = m_pairs[pair].edge_cut; - } - } - - std::cout << "max norm is " << max << std::endl; + QuotientGraphEdges qgraph_edges; + getQuotientGraphEdges(qgraph_edges); + double max = 0; + for( unsigned i = 0; i < qgraph_edges.size(); i++) { + boundary_pair & pair = qgraph_edges[i]; + + if( m_pairs[pair].edge_cut > max ) { + max = m_pairs[pair].edge_cut; + } + } + + std::cout << "max norm is " << max << std::endl; } inline void complete_boundary::getUnderlyingQuotientGraph( graph_access & Q_bar ) { - basicGraph * graphref = new basicGraph; - - if(Q_bar.graphref != NULL) { + basicGraph * graphref = new basicGraph; + + if(Q_bar.graphref != NULL) { delete Q_bar.graphref; - } - Q_bar.graphref = graphref; - - std::vector< std::vector< std::pair > > building_tool; - building_tool.resize(m_block_infos.size()); - - block_pairs::iterator iter; - for(iter = m_pairs.begin(); iter != m_pairs.end(); iter++ ) { - boundary_pair cur_pair = iter->first; - - std::pair qedge_lhs; - qedge_lhs.first = cur_pair.rhs; - qedge_lhs.second = m_pairs[cur_pair].edge_cut; - building_tool[cur_pair.lhs].push_back(qedge_lhs); - - std::pair qedge_rhs; - qedge_rhs.first = cur_pair.lhs; - qedge_rhs.second = m_pairs[cur_pair].edge_cut; - building_tool[cur_pair.rhs].push_back(qedge_rhs); - } - - Q_bar.start_construction(building_tool.size(), 2*m_pairs.size()); - - for( unsigned p = 0; p < building_tool.size(); p++) { - NodeID node = Q_bar.new_node(); - Q_bar.setNodeWeight(node, m_block_infos[p].block_weight); - - for( unsigned j = 0; j < building_tool[p].size(); j++) { - EdgeID e = Q_bar.new_edge(node, building_tool[p][j].first); - Q_bar.setEdgeWeight(e, building_tool[p][j].second); - } - } - - Q_bar.finish_construction(); + } + Q_bar.graphref = graphref; + + std::vector< std::vector< std::pair > > building_tool; + building_tool.resize(m_block_infos.size()); + + block_pairs::iterator iter; + for(iter = m_pairs.begin(); iter != m_pairs.end(); iter++ ) { + boundary_pair cur_pair = iter->first; + + std::pair qedge_lhs; + qedge_lhs.first = cur_pair.rhs; + qedge_lhs.second = m_pairs[cur_pair].edge_cut; + building_tool[cur_pair.lhs].push_back(qedge_lhs); + + std::pair qedge_rhs; + qedge_rhs.first = cur_pair.lhs; + qedge_rhs.second = m_pairs[cur_pair].edge_cut; + building_tool[cur_pair.rhs].push_back(qedge_rhs); + } + + Q_bar.start_construction(building_tool.size(), 2*m_pairs.size()); + + for( unsigned p = 0; p < building_tool.size(); p++) { + NodeID node = Q_bar.new_node(); + Q_bar.setNodeWeight(node, m_block_infos[p].block_weight); + + for( unsigned j = 0; j < building_tool[p].size(); j++) { + EdgeID e = Q_bar.new_edge(node, building_tool[p][j].first); + Q_bar.setEdgeWeight(e, building_tool[p][j].second); + } + } + + Q_bar.finish_construction(); } inline void complete_boundary::getNeighbors(PartitionID & block, std::vector & neighbors) { //lazy if(Q.graphref == NULL) { - getUnderlyingQuotientGraph(Q); + getUnderlyingQuotientGraph(Q); // note that the quotient graph structure currently does not get updated } @@ -421,8 +421,8 @@ inline void complete_boundary::getNeighbors(PartitionID & block, std::vector lhs_neighbors; @@ -437,7 +437,7 @@ void complete_boundary::setup_start_nodes_around_blocks(graph_access & G, PartialBoundary & partial_boundary_lhs = getDirectedBoundary(lhs, lhs, neighbor); forall_boundary_nodes(partial_boundary_lhs, cur_bnd_node) { ASSERT_EQ(G.getPartitionIndex(cur_bnd_node), lhs); - if(allready_contained.find(cur_bnd_node) == allready_contained.end() ) { + if(allready_contained.find(cur_bnd_node) == allready_contained.end() ) { start_nodes.push_back(cur_bnd_node); allready_contained[cur_bnd_node] = true; } @@ -446,7 +446,7 @@ void complete_boundary::setup_start_nodes_around_blocks(graph_access & G, PartialBoundary & partial_boundary_neighbor = getDirectedBoundary(neighbor, lhs, neighbor); forall_boundary_nodes(partial_boundary_neighbor, cur_bnd_node) { ASSERT_EQ(G.getPartitionIndex(cur_bnd_node), neighbor); - if(allready_contained.find(cur_bnd_node) == allready_contained.end()) { + if(allready_contained.find(cur_bnd_node) == allready_contained.end()) { start_nodes.push_back(cur_bnd_node); allready_contained[cur_bnd_node] = true; } @@ -458,7 +458,7 @@ void complete_boundary::setup_start_nodes_around_blocks(graph_access & G, PartialBoundary & partial_boundary_rhs = getDirectedBoundary(rhs, rhs, neighbor); forall_boundary_nodes(partial_boundary_rhs, cur_bnd_node) { ASSERT_EQ(G.getPartitionIndex(cur_bnd_node), rhs); - if(allready_contained.find(cur_bnd_node) == allready_contained.end() ) { + if(allready_contained.find(cur_bnd_node) == allready_contained.end() ) { start_nodes.push_back(cur_bnd_node); allready_contained[cur_bnd_node] = true; } @@ -467,7 +467,7 @@ void complete_boundary::setup_start_nodes_around_blocks(graph_access & G, PartialBoundary & partial_boundary_neighbor = getDirectedBoundary(neighbor, rhs, neighbor); forall_boundary_nodes(partial_boundary_neighbor, cur_bnd_node) { ASSERT_EQ(G.getPartitionIndex(cur_bnd_node), neighbor); - if(allready_contained.find(cur_bnd_node) == allready_contained.end()) { + if(allready_contained.find(cur_bnd_node) == allready_contained.end()) { start_nodes.push_back(cur_bnd_node); allready_contained[cur_bnd_node] = true; } @@ -481,16 +481,16 @@ void complete_boundary::setup_start_nodes_all(graph_access & G, boundary_startin getQuotientGraphEdges(quotient_graph_edges); std::unordered_map allready_contained; - + for( unsigned i = 0; i < quotient_graph_edges.size(); i++) { boundary_pair & ret_value = quotient_graph_edges[i]; - PartitionID lhs = ret_value.lhs; + PartitionID lhs = ret_value.lhs; PartitionID rhs = ret_value.rhs; PartialBoundary & partial_boundary_lhs = getDirectedBoundary(lhs, lhs, rhs); forall_boundary_nodes(partial_boundary_lhs, cur_bnd_node) { ASSERT_EQ(G.getPartitionIndex(cur_bnd_node), lhs); - if(allready_contained.find(cur_bnd_node) == allready_contained.end() ) { + if(allready_contained.find(cur_bnd_node) == allready_contained.end() ) { start_nodes.push_back(cur_bnd_node); allready_contained[cur_bnd_node] = true; } @@ -499,7 +499,7 @@ void complete_boundary::setup_start_nodes_all(graph_access & G, boundary_startin PartialBoundary & partial_boundary_rhs = getDirectedBoundary(rhs, lhs, rhs); forall_boundary_nodes(partial_boundary_rhs, cur_bnd_node) { ASSERT_EQ(G.getPartitionIndex(cur_bnd_node), rhs); - if(allready_contained.find(cur_bnd_node) == allready_contained.end()) { + if(allready_contained.find(cur_bnd_node) == allready_contained.end()) { start_nodes.push_back(cur_bnd_node); allready_contained[cur_bnd_node] = true; } @@ -535,14 +535,14 @@ inline bool complete_boundary::assert_bnodes_in_boundaries() { lhs_part_weight += G.getNodeWeight(n); lhs_no_nodes++; } else if(source_partition == rhs){ - rhs_part_weight += G.getNodeWeight(n); + rhs_part_weight += G.getNodeWeight(n); rhs_no_nodes++; } forall_out_edges(G, e, n) { NodeID targetID = G.getEdgeTarget(e); PartitionID target_partition = G.getPartitionIndex(targetID); - bool is_cut_edge = (source_partition == lhs && target_partition == rhs) + bool is_cut_edge = (source_partition == lhs && target_partition == rhs) || (source_partition == rhs && target_partition == lhs); if(is_cut_edge) { @@ -557,7 +557,7 @@ inline bool complete_boundary::assert_bnodes_in_boundaries() { ASSERT_EQ(m_block_infos[rhs].block_weight, rhs_part_weight); ASSERT_EQ(m_block_infos[lhs].block_no_nodes, lhs_no_nodes); ASSERT_EQ(m_block_infos[rhs].block_no_nodes, rhs_no_nodes); - ASSERT_EQ(m_pairs[bp].edge_cut,edge_cut/2); + ASSERT_EQ(m_pairs[bp].edge_cut,edge_cut/2); } } @@ -567,12 +567,12 @@ inline bool complete_boundary::assert_bnodes_in_boundaries() { inline bool complete_boundary::assert_boundaries_are_bnodes() { graph_access & G = *m_graph_ref; forall_nodes(G, n) { - PartitionID partition = G.getPartitionIndex(n); - forall_out_edges(G, e, n) { - NodeID target = G.getEdgeTarget(e); - PartitionID targets_partition = G.getPartitionIndex(target); + PartitionID partition = G.getPartitionIndex(n); + forall_out_edges(G, e, n) { + NodeID target = G.getEdgeTarget(e); + PartitionID targets_partition = G.getPartitionIndex(target); - if(partition != targets_partition) { + if(partition != targets_partition) { boundary_pair bp; bp.k = G.get_partition_count(); bp.lhs = partition; @@ -581,19 +581,19 @@ inline bool complete_boundary::assert_boundaries_are_bnodes() { ASSERT_TRUE(contains(n, partition, &bp)); ASSERT_TRUE(contains(target, targets_partition, &bp)); - } - } endfor - - } endfor - QuotientGraphEdges qgraph_edges; - getQuotientGraphEdges(qgraph_edges); - for( unsigned i = 0; i < qgraph_edges.size(); i++) { - boundary_pair & pair = qgraph_edges[i]; - ASSERT_NEQ(pair.lhs, pair.rhs); - } + } + } endfor + + } endfor + QuotientGraphEdges qgraph_edges; + getQuotientGraphEdges(qgraph_edges); + for( unsigned i = 0; i < qgraph_edges.size(); i++) { + boundary_pair & pair = qgraph_edges[i]; + ASSERT_NEQ(pair.lhs, pair.rhs); + } return true; } #endif // #ifndef NDEBUG - +} #endif /* end of include guard: COMPLETE_BOUNDARY_URZZFDEI */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/boundary_bfs.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/boundary_bfs.cpp index 4338a2d7..788ed4b2 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/boundary_bfs.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/boundary_bfs.cpp @@ -10,7 +10,7 @@ #include "boundary_bfs.h" #include "random_functions.h" - +namespace kahip::modified { boundary_bfs::boundary_bfs() { } @@ -27,55 +27,55 @@ bool boundary_bfs::boundary_bfs_search(graph_access & G, NodeWeight & stripe_weight, bool flow_tiebreaking) { - std::queue node_queue; - std::vector deepth(G.number_of_nodes(), -1); - int cur_deepth = 0; - - if(flow_tiebreaking) { - random_functions::permutate_vector_good(start_nodes, false); - } - /*************************** - * Initialize the Queue - * *************************/ - NodeWeight accumulated_weight = 0; - for(unsigned int i = 0; i < start_nodes.size(); i++) { - node_queue.push(start_nodes[i]); - ASSERT_TRUE(G.getPartitionIndex(start_nodes[i]) == partition); - deepth[start_nodes[i]] = cur_deepth; - reached_nodes.push_back(start_nodes[i]); - accumulated_weight += G.getNodeWeight(start_nodes[i]); - } - ++cur_deepth; + std::queue node_queue; + std::vector deepth(G.number_of_nodes(), -1); + int cur_deepth = 0; + + if(flow_tiebreaking) { + random_functions::permutate_vector_good(start_nodes, false); + } + /*************************** + * Initialize the Queue + * *************************/ + NodeWeight accumulated_weight = 0; + for(unsigned int i = 0; i < start_nodes.size(); i++) { + node_queue.push(start_nodes[i]); + ASSERT_TRUE(G.getPartitionIndex(start_nodes[i]) == partition); + deepth[start_nodes[i]] = cur_deepth; + reached_nodes.push_back(start_nodes[i]); + accumulated_weight += G.getNodeWeight(start_nodes[i]); + } + ++cur_deepth; - if(accumulated_weight >= upper_bound_no_nodes) { - stripe_weight = accumulated_weight; - return false; - } - /*************************** - * Do the BFS - ***************************/ - while (!node_queue.empty()) { - if(accumulated_weight >= upper_bound_no_nodes) break; - NodeID n = node_queue.front(); - node_queue.pop(); + if(accumulated_weight >= upper_bound_no_nodes) { + stripe_weight = accumulated_weight; + return false; + } + /*************************** + * Do the BFS + ***************************/ + while (!node_queue.empty()) { + if(accumulated_weight >= upper_bound_no_nodes) break; + NodeID n = node_queue.front(); + node_queue.pop(); - if (deepth[n] == cur_deepth) { - cur_deepth++; - } - forall_out_edges(G,e,n) { - NodeID t = G.getEdgeTarget(e); - if(deepth[t] == -1 && G.getPartitionIndex(t) == partition + if (deepth[n] == cur_deepth) { + cur_deepth++; + } + forall_out_edges(G,e,n) { + NodeID t = G.getEdgeTarget(e); + if(deepth[t] == -1 && G.getPartitionIndex(t) == partition && accumulated_weight + G.getNodeWeight(t) <= upper_bound_no_nodes) { - deepth[t] = cur_deepth; - node_queue.push(t); - reached_nodes.push_back(t); - accumulated_weight += G.getNodeWeight(t); - } - } endfor - } - bool some_to_do = stripe_weight != accumulated_weight; - stripe_weight = accumulated_weight; - return some_to_do; + deepth[t] = cur_deepth; + node_queue.push(t); + reached_nodes.push_back(t); + accumulated_weight += G.getNodeWeight(t); + } + } endfor + } + bool some_to_do = stripe_weight != accumulated_weight; + stripe_weight = accumulated_weight; + return some_to_do; } - +} diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/boundary_bfs.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/boundary_bfs.h index 872f9f4d..7e595d1c 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/boundary_bfs.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/boundary_bfs.h @@ -10,20 +10,20 @@ #include "data_structure/graph_access.h" #include "partition_config.h" - +namespace kahip::modified { class boundary_bfs { - public: - boundary_bfs( ); - virtual ~boundary_bfs(); +public: + boundary_bfs( ); + virtual ~boundary_bfs(); - bool boundary_bfs_search(graph_access & G, - std::vector & start_nodes, - PartitionID partition, - NodeWeight upper_bound_no_nodes, - std::vector & reached_nodes, - NodeWeight & stripe_weight, - bool flow_tiebreaking); + bool boundary_bfs_search(graph_access & G, + std::vector & start_nodes, + PartitionID partition, + NodeWeight upper_bound_no_nodes, + std::vector & reached_nodes, + NodeWeight & stripe_weight, + bool flow_tiebreaking); }; - +} #endif /* end of include guard: BOUNDARY_BFS_4AJLJJAB */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/edge_cut_flow_solver.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/edge_cut_flow_solver.cpp index 2d38ef2a..5ad0dbd2 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/edge_cut_flow_solver.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/edge_cut_flow_solver.cpp @@ -16,8 +16,7 @@ #include "edge_cut_flow_solver.h" #include "flow_macros.h" #include "most_balanced_minimum_cuts/most_balanced_minimum_cuts.h" - - +namespace kahip::modified { edge_cut_flow_solver::edge_cut_flow_solver() { } @@ -32,158 +31,158 @@ EdgeID edge_cut_flow_solver::regions_no_edges( graph_access & G, std::vector & outer_lhs_boundary_nodes, std::vector & outer_rhs_boundary_nodes ) { - EdgeID no_of_edges = 0; - unsigned idx = 0; - for( unsigned i = 0; i < lhs_boundary_stripe.size(); i++, idx++) { - NodeID node = lhs_boundary_stripe[i]; - bool is_outer_boundary = false; - forall_out_edges(G, e, node) { - if(G.getPartitionIndex(G.getEdgeTarget(e)) == BOUNDARY_STRIPE_NODE) no_of_edges++; - else is_outer_boundary = true; - } endfor - if(is_outer_boundary) { - outer_lhs_boundary_nodes.push_back(idx); - } - } - - for( unsigned i = 0; i < rhs_boundary_stripe.size(); i++, idx++) { - NodeID node = rhs_boundary_stripe[i]; - bool is_outer_boundary = false; - forall_out_edges(G, e, node) { - if(G.getPartitionIndex(G.getEdgeTarget(e)) == BOUNDARY_STRIPE_NODE) no_of_edges++; - else is_outer_boundary = true; - } endfor - if(is_outer_boundary) { - outer_rhs_boundary_nodes.push_back(idx); - } - } - - return no_of_edges; + EdgeID no_of_edges = 0; + unsigned idx = 0; + for( unsigned i = 0; i < lhs_boundary_stripe.size(); i++, idx++) { + NodeID node = lhs_boundary_stripe[i]; + bool is_outer_boundary = false; + forall_out_edges(G, e, node) { + if(G.getPartitionIndex(G.getEdgeTarget(e)) == BOUNDARY_STRIPE_NODE) no_of_edges++; + else is_outer_boundary = true; + } endfor + if(is_outer_boundary) { + outer_lhs_boundary_nodes.push_back(idx); + } + } + + for( unsigned i = 0; i < rhs_boundary_stripe.size(); i++, idx++) { + NodeID node = rhs_boundary_stripe[i]; + bool is_outer_boundary = false; + forall_out_edges(G, e, node) { + if(G.getPartitionIndex(G.getEdgeTarget(e)) == BOUNDARY_STRIPE_NODE) no_of_edges++; + else is_outer_boundary = true; + } endfor + if(is_outer_boundary) { + outer_rhs_boundary_nodes.push_back(idx); + } + } + + return no_of_edges; } -EdgeWeight edge_cut_flow_solver::convert_ds( const PartitionConfig & config, - graph_access & G, - PartitionID & lhs, - PartitionID & rhs, +EdgeWeight edge_cut_flow_solver::convert_ds( const PartitionConfig & config, + graph_access & G, + PartitionID & lhs, + PartitionID & rhs, std::vector & lhs_boundary_stripe, std::vector & rhs_boundary_stripe, - std::vector & new_to_old_ids, - long *n_ad, - long* m_ad, - node** nodes_ad, - arc** arcs_ad, + std::vector & new_to_old_ids, + long *n_ad, + long* m_ad, + node** nodes_ad, + arc** arcs_ad, long ** cap_ad, - node** source_ad, - node** sink_ad, + node** source_ad, + node** sink_ad, long* node_min_ad, EdgeID & no_edges_in_flow_graph) { - //should soon be refactored - #include "convert_ds_variables.h" - - //building up the graph as in parse.h of hi_pr code - NodeID idx = 0; - new_to_old_ids.resize(lhs_boundary_stripe.size() + rhs_boundary_stripe.size()); - std::unordered_map old_to_new; - for( unsigned i = 0; i < lhs_boundary_stripe.size(); i++) { - G.setPartitionIndex(lhs_boundary_stripe[i], BOUNDARY_STRIPE_NODE); - new_to_old_ids[idx] = lhs_boundary_stripe[i]; - old_to_new[lhs_boundary_stripe[i]] = idx++ ; - } - for( unsigned i = 0; i < rhs_boundary_stripe.size(); i++) { - G.setPartitionIndex(rhs_boundary_stripe[i], BOUNDARY_STRIPE_NODE); - new_to_old_ids[idx] = rhs_boundary_stripe[i]; - old_to_new[rhs_boundary_stripe[i]] = idx++; - } + //should soon be refactored +#include "convert_ds_variables.h" + + //building up the graph as in parse.h of hi_pr code + NodeID idx = 0; + new_to_old_ids.resize(lhs_boundary_stripe.size() + rhs_boundary_stripe.size()); + std::unordered_map old_to_new; + for( unsigned i = 0; i < lhs_boundary_stripe.size(); i++) { + G.setPartitionIndex(lhs_boundary_stripe[i], BOUNDARY_STRIPE_NODE); + new_to_old_ids[idx] = lhs_boundary_stripe[i]; + old_to_new[lhs_boundary_stripe[i]] = idx++ ; + } + for( unsigned i = 0; i < rhs_boundary_stripe.size(); i++) { + G.setPartitionIndex(rhs_boundary_stripe[i], BOUNDARY_STRIPE_NODE); + new_to_old_ids[idx] = rhs_boundary_stripe[i]; + old_to_new[rhs_boundary_stripe[i]] = idx++; + } + + std::vector outer_lhs_boundary; + std::vector outer_rhs_boundary; + EdgeID no_edges = regions_no_edges(G, lhs_boundary_stripe, rhs_boundary_stripe, + lhs, rhs, + outer_lhs_boundary, outer_rhs_boundary); + no_edges_in_flow_graph = no_edges; + + if(outer_lhs_boundary.size() == 0 || outer_rhs_boundary.size() == 0) return false; + n = lhs_boundary_stripe.size() + rhs_boundary_stripe.size() + 2; //+source and target + m = no_edges + outer_lhs_boundary.size() + outer_rhs_boundary.size(); + + nodes = (node*) calloc ( n+2, sizeof(node) ); + arcs = (arc*) calloc ( 2*m+1, sizeof(arc) ); + arc_tail = (long*) calloc ( 2*m, sizeof(long) ); + arc_first= (long*) calloc ( n+2, sizeof(long) ); + acap = (long*) calloc ( 2*m, sizeof(long) ); + arc_current = arcs; + + node_max = 0; + node_min = n; + + unsigned nodeoffset = 1; + source = n - 2 + nodeoffset; + sink = source+1; + idx = 0; + for( unsigned i = 0; i < lhs_boundary_stripe.size(); i++, idx++) { + NodeID node = lhs_boundary_stripe[i]; + NodeID sourceID = idx + nodeoffset; + forall_out_edges(G, e, node) { + if(G.getPartitionIndex(G.getEdgeTarget(e)) == BOUNDARY_STRIPE_NODE) { + NodeID targetID = old_to_new[G.getEdgeTarget(e)] + nodeoffset; + EdgeWeight capacity = G.getEdgeWeight(e); + tail = sourceID; + head = targetID; + cap = capacity; + + createEdge() +} + } endfor +} - std::vector outer_lhs_boundary; - std::vector outer_rhs_boundary; - EdgeID no_edges = regions_no_edges(G, lhs_boundary_stripe, rhs_boundary_stripe, - lhs, rhs, - outer_lhs_boundary, outer_rhs_boundary); - no_edges_in_flow_graph = no_edges; - - if(outer_lhs_boundary.size() == 0 || outer_rhs_boundary.size() == 0) return false; - n = lhs_boundary_stripe.size() + rhs_boundary_stripe.size() + 2; //+source and target - m = no_edges + outer_lhs_boundary.size() + outer_rhs_boundary.size(); - - nodes = (node*) calloc ( n+2, sizeof(node) ); - arcs = (arc*) calloc ( 2*m+1, sizeof(arc) ); - arc_tail = (long*) calloc ( 2*m, sizeof(long) ); - arc_first= (long*) calloc ( n+2, sizeof(long) ); - acap = (long*) calloc ( 2*m, sizeof(long) ); - arc_current = arcs; - - node_max = 0; - node_min = n; - - unsigned nodeoffset = 1; - source = n - 2 + nodeoffset; - sink = source+1; - idx = 0; - for( unsigned i = 0; i < lhs_boundary_stripe.size(); i++, idx++) { - NodeID node = lhs_boundary_stripe[i]; - NodeID sourceID = idx + nodeoffset; - forall_out_edges(G, e, node) { - if(G.getPartitionIndex(G.getEdgeTarget(e)) == BOUNDARY_STRIPE_NODE) { - NodeID targetID = old_to_new[G.getEdgeTarget(e)] + nodeoffset; - EdgeWeight capacity = G.getEdgeWeight(e); - tail = sourceID; - head = targetID; - cap = capacity; - - createEdge() - } - } endfor - } + for( unsigned i = 0; i < rhs_boundary_stripe.size(); i++, idx++) { + NodeID node = rhs_boundary_stripe[i]; + NodeID sourceID = idx + nodeoffset; + forall_out_edges(G, e, node) { + if(G.getPartitionIndex(G.getEdgeTarget(e)) == BOUNDARY_STRIPE_NODE) { + NodeID targetID = old_to_new[G.getEdgeTarget(e)] + nodeoffset; + EdgeWeight capacity = G.getEdgeWeight(e); + tail = sourceID; + head = targetID; + cap = capacity; + + createEdge() +} + } endfor +} - for( unsigned i = 0; i < rhs_boundary_stripe.size(); i++, idx++) { - NodeID node = rhs_boundary_stripe[i]; - NodeID sourceID = idx + nodeoffset; - forall_out_edges(G, e, node) { - if(G.getPartitionIndex(G.getEdgeTarget(e)) == BOUNDARY_STRIPE_NODE) { - NodeID targetID = old_to_new[G.getEdgeTarget(e)] + nodeoffset; - EdgeWeight capacity = G.getEdgeWeight(e); - tail = sourceID; - head = targetID; - cap = capacity; - - createEdge() - } - } endfor - } + //connect source and target with outer boundary nodes + long max_capacity = std::numeric_limits::max(); + for(unsigned i = 0; i < outer_lhs_boundary.size(); i++) { + NodeID targetID = outer_lhs_boundary[i]+ nodeoffset; + tail = source; + head = targetID; + cap = max_capacity; - //connect source and target with outer boundary nodes - long max_capacity = std::numeric_limits::max(); - for(unsigned i = 0; i < outer_lhs_boundary.size(); i++) { - NodeID targetID = outer_lhs_boundary[i]+ nodeoffset; - tail = source; - head = targetID; - cap = max_capacity; + createEdge() +} - createEdge() - } + for(unsigned i = 0; i < outer_rhs_boundary.size(); i++) { + NodeID sourceID = outer_rhs_boundary[i]+ nodeoffset; + tail = sourceID; + head = sink; + cap = max_capacity; - for(unsigned i = 0; i < outer_rhs_boundary.size(); i++) { - NodeID sourceID = outer_rhs_boundary[i]+ nodeoffset; - tail = sourceID; - head = sink; - cap = max_capacity; + createEdge() +} - createEdge() - } + //this is so dirty ;) +#include "linear_ordering_n_assign.h" - //this is so dirty ;) - #include "linear_ordering_n_assign.h" - - /* Thanks God! all is done */ - return true; + /* Thanks God! all is done */ + return true; } -EdgeWeight edge_cut_flow_solver::get_min_flow_max_cut(const PartitionConfig & config, - graph_access & G, - PartitionID & lhs, - PartitionID & rhs, +EdgeWeight edge_cut_flow_solver::get_min_flow_max_cut(const PartitionConfig & config, + graph_access & G, + PartitionID & lhs, + PartitionID & rhs, std::vector & lhs_boundary_stripe, std::vector & rhs_boundary_stripe, std::vector & new_to_old_ids, @@ -192,112 +191,112 @@ EdgeWeight edge_cut_flow_solver::get_min_flow_max_cut(const PartitionConfig & co NodeWeight & rhs_stripe_weight, std::vector & new_rhs_nodes) { - node *j = NULL; - int cc; - bucket *l; - - - globUpdtFreq = GLOB_UPDT_FREQ; - - EdgeID no_edges_in_flow_graph = 0; - bool do_something = convert_ds(config, G, lhs, rhs, lhs_boundary_stripe, rhs_boundary_stripe, new_to_old_ids, &n, - &m, - &nodes, - &arcs, - &cap, - &source, - &sink, - &nMin, - no_edges_in_flow_graph ); - - if(!do_something) return initial_cut; - - cc = internal_allocDS(); - if ( cc ) { fprintf ( stderr, "Allocation error\n"); exit ( 1 ); } - - internal_init(); - internal_stage_one( ); - - if(config.most_balanced_minimum_cuts) { - internal_stage_two(); - } - - /* check if mincut is saturated */ - aMax = dMax = 0; - for (l = buckets; l < buckets + n; l++) { - l->firstActive = sentinelNode; - l->firstInactive = sentinelNode; - } - internal_global_update(); - - if(!config.most_balanced_minimum_cuts) { - forAllNodes(j) { - if (j->d < n) { - new_rhs_nodes.push_back(nNode(j)-1); - } + node *j = NULL; + int cc; + bucket *l; + + + globUpdtFreq = GLOB_UPDT_FREQ; + + EdgeID no_edges_in_flow_graph = 0; + bool do_something = convert_ds(config, G, lhs, rhs, lhs_boundary_stripe, rhs_boundary_stripe, new_to_old_ids, &n, + &m, + &nodes, + &arcs, + &cap, + &source, + &sink, + &nMin, + no_edges_in_flow_graph ); + + if(!do_something) return initial_cut; + + cc = internal_allocDS(); + if ( cc ) { fprintf ( stderr, "Allocation error\n"); exit ( 1 ); } + + internal_init(); + internal_stage_one( ); + + if(config.most_balanced_minimum_cuts) { + internal_stage_two(); + } + + /* check if mincut is saturated */ + aMax = dMax = 0; + for (l = buckets; l < buckets + n; l++) { + l->firstActive = sentinelNode; + l->firstInactive = sentinelNode; + } + internal_global_update(); + + if(!config.most_balanced_minimum_cuts) { + forAllNodes(j) { + if (j->d < n) { + new_rhs_nodes.push_back(nNode(j)-1); + } + } + } else { + node *i; + node *t; + long ni, na, flow_value, back_flow_value; + arc *a; + arc *innerStopA; + + NodeID no_nodes_flow_graph = lhs_boundary_stripe.size() + rhs_boundary_stripe.size()+2; + graph_access residualGraph; + residualGraph.start_construction(no_nodes_flow_graph,2*no_edges_in_flow_graph); + + //(u.v) \in E iff (u,v) in E and f_uv < c(u,v) + // or (v,u) in E and f_vu > 0 + forAllNodes(i) { + ni = nNode(i); + NodeID node = residualGraph.new_node(); // for each node here create a new node + + if( (unsigned)(ni - 1) < new_to_old_ids.size()) { //note: unsigned has been introduced without testing + residualGraph.setNodeWeight( node, G.getNodeWeight(new_to_old_ids[ni-1])); + } + + forAllArcs(i,a) { + na = nArc(a); + if ( cap[na] > 0 ) { + flow_value = cap[na] - a->resCap; + + if( flow_value < cap[na] ) { + //create that edge + residualGraph.new_edge(node, nNode(a->head)-1); + } else { + //check wether backwards edge has positive flow + t = a->head; + arc* outarc; + bool prev_found = false; + //we cannot use the makro here because it would overwrite stopA! + for (outarc = t->first, innerStopA = (t+1)->first; outarc != innerStopA; outarc++) { + if(nNode(outarc->head) == ni) { + + back_flow_value = cap[nArc(outarc)] - outarc->resCap; + if(back_flow_value > 0) { + residualGraph.new_edge(node, nNode(a->head)-1); + break; } - } else { - node *i; - node *t; - long ni, na, flow_value, back_flow_value; - arc *a; - arc *innerStopA; - - NodeID no_nodes_flow_graph = lhs_boundary_stripe.size() + rhs_boundary_stripe.size()+2; - graph_access residualGraph; - residualGraph.start_construction(no_nodes_flow_graph,2*no_edges_in_flow_graph); - - //(u.v) \in E iff (u,v) in E and f_uv < c(u,v) - // or (v,u) in E and f_vu > 0 - forAllNodes(i) { - ni = nNode(i); - NodeID node = residualGraph.new_node(); // for each node here create a new node - - if( (unsigned)(ni - 1) < new_to_old_ids.size()) { //note: unsigned has been introduced without testing - residualGraph.setNodeWeight( node, G.getNodeWeight(new_to_old_ids[ni-1])); - } - - forAllArcs(i,a) { - na = nArc(a); - if ( cap[na] > 0 ) { - flow_value = cap[na] - a->resCap; - - if( flow_value < cap[na] ) { - //create that edge - residualGraph.new_edge(node, nNode(a->head)-1); - } else { - //check wether backwards edge has positive flow - t = a->head; - arc* outarc; - bool prev_found = false; - //we cannot use the makro here because it would overwrite stopA! - for (outarc = t->first, innerStopA = (t+1)->first; outarc != innerStopA; outarc++) { - if(nNode(outarc->head) == ni) { - - back_flow_value = cap[nArc(outarc)] - outarc->resCap; - if(back_flow_value > 0) { - residualGraph.new_edge(node, nNode(a->head)-1); - break; - } - if(prev_found) { - break; - } - prev_found = true; - } - } - - } - } - } + if(prev_found) { + break; } + prev_found = true; + } + } - residualGraph.finish_construction(); - NodeWeight average_partition_weight = ceil(config.largest_graph_weight / config.k); - NodeWeight perfect_rhs_stripe_weight = abs((int)average_partition_weight - (int)rhs_part_weight+(int) rhs_stripe_weight); - - most_balanced_minimum_cuts mbmc; - mbmc.compute_good_balanced_min_cut(residualGraph, config, perfect_rhs_stripe_weight, new_rhs_nodes); + } } - return flow; -} + } + } + + residualGraph.finish_construction(); + NodeWeight average_partition_weight = ceil(config.largest_graph_weight / config.k); + NodeWeight perfect_rhs_stripe_weight = abs((int)average_partition_weight - (int)rhs_part_weight+(int) rhs_stripe_weight); + most_balanced_minimum_cuts mbmc; + mbmc.compute_good_balanced_min_cut(residualGraph, config, perfect_rhs_stripe_weight, new_rhs_nodes); + } + return flow; +} +} diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/edge_cut_flow_solver.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/edge_cut_flow_solver.h index 45f7c6d6..53007ac0 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/edge_cut_flow_solver.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/edge_cut_flow_solver.h @@ -9,54 +9,53 @@ #define EDGE_FLOW_SOLVER_4P49OMM #include "flow_solver.h" - +namespace kahip::modified { class edge_cut_flow_solver : public flow_solver { - public: - edge_cut_flow_solver( ); - virtual ~edge_cut_flow_solver(); - - EdgeWeight get_min_flow_max_cut(const PartitionConfig & config, - graph_access & G, - PartitionID & lhs, - PartitionID & rhs, - std::vector & lhs_boundary_stripe, - std::vector & rhs_boundary_stripe, - std::vector & new_to_old_ids, - EdgeWeight & initial_cut, - NodeWeight & rhs_part_weight, - NodeWeight & rhs_stripe_weight, - std::vector & new_rhs_nodes); - - EdgeID regions_no_edges(graph_access & G, - std::vector & lhs_boundary_stripe, - std::vector & rhs_boundary_stripe, - PartitionID & lhs, - PartitionID & rhs, - std::vector & outer_lhs_boundary_nodes, - std::vector & outer_rhs_boundary_nodes ); - - - //modified parse code from hi_pr - EdgeWeight convert_ds(const PartitionConfig & config, - graph_access & G, - PartitionID & lhs, - PartitionID & rhs, - std::vector & lhs_boundary_stripe, - std::vector & rhs_boundary_stripe, - std::vector & new_to_old_ids, - long *n_ad, - long* m_ad, - node** nodes_ad, - arc** arcs_ad, - long ** cap_ad, - node** source_ad, - node** sink_ad, - long* node_min_ad, - EdgeID & no_edge_in_flow_graph); +public: + edge_cut_flow_solver( ); + virtual ~edge_cut_flow_solver(); + + EdgeWeight get_min_flow_max_cut(const PartitionConfig & config, + graph_access & G, + PartitionID & lhs, + PartitionID & rhs, + std::vector & lhs_boundary_stripe, + std::vector & rhs_boundary_stripe, + std::vector & new_to_old_ids, + EdgeWeight & initial_cut, + NodeWeight & rhs_part_weight, + NodeWeight & rhs_stripe_weight, + std::vector & new_rhs_nodes); + + EdgeID regions_no_edges(graph_access & G, + std::vector & lhs_boundary_stripe, + std::vector & rhs_boundary_stripe, + PartitionID & lhs, + PartitionID & rhs, + std::vector & outer_lhs_boundary_nodes, + std::vector & outer_rhs_boundary_nodes ); + + + //modified parse code from hi_pr + EdgeWeight convert_ds(const PartitionConfig & config, + graph_access & G, + PartitionID & lhs, + PartitionID & rhs, + std::vector & lhs_boundary_stripe, + std::vector & rhs_boundary_stripe, + std::vector & new_to_old_ids, + long *n_ad, + long* m_ad, + node** nodes_ad, + arc** arcs_ad, + long ** cap_ad, + node** source_ad, + node** sink_ad, + long* node_min_ad, + EdgeID & no_edge_in_flow_graph); }; - - +} #endif /* end of include guard: FLOW_SOLVER_4P49OMM */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/flow_macros.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/flow_macros.h index 1d234a14..d1049f17 100755 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/flow_macros.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/flow_macros.h @@ -34,8 +34,6 @@ #define nNode( i ) ( (i) - nodes + nMin ) #define nArc( a ) ( ( a == NULL )? -1 : (a) - arcs ) -#define min( a, b ) ( ( (a) < (b) ) ? a : b ) - #define createEdge()\ {\ arc_first[tail + 1] ++; \ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/flow_solver.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/flow_solver.cpp index 37131c6c..34c4040b 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/flow_solver.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/flow_solver.cpp @@ -14,430 +14,429 @@ #include #include #include -#include +#include #include #include "flow_solver.h" #include "flow_macros.h" #include "most_balanced_minimum_cuts/most_balanced_minimum_cuts.h" - - +namespace kahip::modified { flow_solver::flow_solver() { - pushCnt = 0; /* number of pushes */ - relabelCnt = 0; /* number of internal_relabels */ - updateCnt = 0; /* number of updates */ - gapCnt = 0; /* number of internal_gaps */ - gNodeCnt = 0; /* number of nodes after internal_gap */ - workSinceUpdate = 0; /* the number of arc scans since last update */ - nodes = NULL; - arcs = NULL; - cap = NULL; - buckets = NULL; - free_nodes = NULL; + pushCnt = 0; /* number of pushes */ + relabelCnt = 0; /* number of internal_relabels */ + updateCnt = 0; /* number of updates */ + gapCnt = 0; /* number of internal_gaps */ + gNodeCnt = 0; /* number of nodes after internal_gap */ + workSinceUpdate = 0; /* the number of arc scans since last update */ + nodes = NULL; + arcs = NULL; + cap = NULL; + buckets = NULL; + free_nodes = NULL; } flow_solver::~flow_solver() { - free(arcs); - free(cap); - free(buckets); - free(free_nodes); + free(arcs); + free(cap); + free(buckets); + free(free_nodes); } void flow_solver::internal_stage_one() { - node *i; - bucket *l; /* current bucket */ + node *i; + bucket *l; /* current bucket */ #if defined(INIT_UPDATE) || defined(OLD_INIT) || defined(WAVE_INIT) - internal_global_update (); + internal_global_update (); #endif - workSinceUpdate = 0; + workSinceUpdate = 0; #ifdef WAVE_INIT - internal_wave(); -#endif + internal_wave(); +#endif - /* main loop */ - while ( aMax >= aMin ) { - l = buckets + aMax; - i = l->firstActive; + /* main loop */ + while ( aMax >= aMin ) { + l = buckets + aMax; + i = l->firstActive; - if (i == sentinelNode) { - aMax--; - } - else { - aRemove(l,i); - assert(i->excess > 0); - internal_discharge (i); - - if (aMax < aMin) - break; - - /* is it time for global update? */ - if (workSinceUpdate * globUpdtFreq > nm) { - internal_global_update (); - workSinceUpdate = 0; - } + if (i == sentinelNode) { + aMax--; + } + else { + aRemove(l,i); + assert(i->excess > 0); + internal_discharge (i); - } + if (aMax < aMin) + break; + + /* is it time for global update? */ + if (workSinceUpdate * globUpdtFreq > nm) { + internal_global_update (); + workSinceUpdate = 0; + } + + } - } /* end of the main loop */ + } /* end of the main loop */ - flow = sink -> excess; -} + flow = sink -> excess; +} void flow_solver::internal_stage_two() { - node *i, *j, *tos, *bos, *restart, *r; - arc *a; - cType delta; - - /* deal with self-loops */ - forAllNodes(i) { - forAllArcs(i,a) - if ( a -> head == i ) { - a -> resCap = cap[a - arcs]; + node *i, *j, *tos, *bos, *restart, *r; + arc *a; + cType delta; + + /* deal with self-loops */ + forAllNodes(i) { + forAllArcs(i,a) + if ( a -> head == i ) { + a -> resCap = cap[a - arcs]; + } + } + + /* initialize */ + tos = bos = NULL; + forAllNodes(i) { + i -> d = WHITE; + // buckets[i-nodes].firstActive = NULL; + buckets[i-nodes].firstActive = sentinelNode; + i -> current = i -> first; + } + + /* eliminate flow cycles, topologicaly order vertices */ + forAllNodes(i) + if (( i -> d == WHITE ) && ( i -> excess > 0 ) && + ( i != source ) && ( i != sink )) { + r = i; + r -> d = GREY; + do { + for ( ; i->current != (i+1)->first; i->current++) { + a = i -> current; + if (( cap[a - arcs] == 0 ) && ( a -> resCap > 0 )) { + j = a -> head; + if ( j -> d == WHITE ) { + /* start scanning j */ + j -> d = GREY; + buckets[j-nodes].firstActive = i; + i = j; + break; + } + else + if ( j -> d == GREY ) { + /* find minimum flow on the cycle */ + delta = a -> resCap; + while ( 1 ) { + delta = std::min( delta, j -> current -> resCap ); + if ( j == i ) + break; + else + j = j -> current -> head; + } + + /* remove delta flow units */ + j = i; + while ( 1 ) { + a = j -> current; + a -> resCap -= delta; + a -> rev -> resCap += delta; + j = a -> head; + if ( j == i ) + break; + } + + /* backup DFS to the first saturated arc */ + restart = i; + for ( j = i -> current -> head; j != i; j = a -> head ) { + a = j -> current; + if (( j -> d == WHITE ) || ( a -> resCap == 0 )) { + j -> current -> head -> d = WHITE; + if ( j -> d != WHITE ) + restart = j; } - } + } - /* initialize */ - tos = bos = NULL; - forAllNodes(i) { - i -> d = WHITE; - // buckets[i-nodes].firstActive = NULL; - buckets[i-nodes].firstActive = sentinelNode; - i -> current = i -> first; - } - - /* eliminate flow cycles, topologicaly order vertices */ - forAllNodes(i) - if (( i -> d == WHITE ) && ( i -> excess > 0 ) && - ( i != source ) && ( i != sink )) { - r = i; - r -> d = GREY; - do { - for ( ; i->current != (i+1)->first; i->current++) { - a = i -> current; - if (( cap[a - arcs] == 0 ) && ( a -> resCap > 0 )) { - j = a -> head; - if ( j -> d == WHITE ) { - /* start scanning j */ - j -> d = GREY; - buckets[j-nodes].firstActive = i; - i = j; - break; - } - else - if ( j -> d == GREY ) { - /* find minimum flow on the cycle */ - delta = a -> resCap; - while ( 1 ) { - delta = min ( delta, j -> current -> resCap ); - if ( j == i ) - break; - else - j = j -> current -> head; - } - - /* remove delta flow units */ - j = i; - while ( 1 ) { - a = j -> current; - a -> resCap -= delta; - a -> rev -> resCap += delta; - j = a -> head; - if ( j == i ) - break; - } - - /* backup DFS to the first saturated arc */ - restart = i; - for ( j = i -> current -> head; j != i; j = a -> head ) { - a = j -> current; - if (( j -> d == WHITE ) || ( a -> resCap == 0 )) { - j -> current -> head -> d = WHITE; - if ( j -> d != WHITE ) - restart = j; - } - } - - if ( restart != i ) { - i = restart; - i->current++; - break; - } - } - } - } - - if (i->current == (i+1)->first) { - /* scan of i complete */ - i -> d = BLACK; - if ( i != source ) { - if ( bos == NULL ) { - bos = i; - tos = i; - } - else { - i -> bNext = tos; - tos = i; - } - } - - if ( i != r ) { - i = buckets[i-nodes].firstActive; - i->current++; - } - else - break; - } - } while ( 1 ); + if ( restart != i ) { + i = restart; + i->current++; + break; + } + } } - - - /* return excesses */ - /* note that sink is not on the stack */ - if ( bos != NULL ) { - for ( i = tos; i != bos; i = i -> bNext ) { - a = i -> first; - while ( i -> excess > 0 ) { - if (( cap[a - arcs] == 0 ) && ( a -> resCap > 0 )) { - if (a->resCap < i->excess) - delta = a->resCap; - else - delta = i->excess; - a -> resCap -= delta; - a -> rev -> resCap += delta; - i -> excess -= delta; - a -> head -> excess += delta; - } - a++; - } + } + + if (i->current == (i+1)->first) { + /* scan of i complete */ + i -> d = BLACK; + if ( i != source ) { + if ( bos == NULL ) { + bos = i; + tos = i; + } + else { + i -> bNext = tos; + tos = i; + } } - /* now do the bottom */ - i = bos; - a = i -> first; - while ( i -> excess > 0 ) { - if (( cap[a - arcs] == 0 ) && ( a -> resCap > 0 )) { - if (a->resCap < i->excess) - delta = a->resCap; - else - delta = i->excess; - a -> resCap -= delta; - a -> rev -> resCap += delta; - i -> excess -= delta; - a -> head -> excess += delta; - } - a++; + + if ( i != r ) { + i = buckets[i-nodes].firstActive; + i->current++; } + else + break; + } + } while ( 1 ); + } + + + /* return excesses */ + /* note that sink is not on the stack */ + if ( bos != NULL ) { + for ( i = tos; i != bos; i = i -> bNext ) { + a = i -> first; + while ( i -> excess > 0 ) { + if (( cap[a - arcs] == 0 ) && ( a -> resCap > 0 )) { + if (a->resCap < i->excess) + delta = a->resCap; + else + delta = i->excess; + a -> resCap -= delta; + a -> rev -> resCap += delta; + i -> excess -= delta; + a -> head -> excess += delta; } + a++; + } + } + /* now do the bottom */ + i = bos; + a = i -> first; + while ( i -> excess > 0 ) { + if (( cap[a - arcs] == 0 ) && ( a -> resCap > 0 )) { + if (a->resCap < i->excess) + delta = a->resCap; + else + delta = i->excess; + a -> resCap -= delta; + a -> rev -> resCap += delta; + i -> excess -= delta; + a -> head -> excess += delta; + } + a++; + } + } } void flow_solver::internal_global_update() { - node *i, *j; /* node pointers */ - arc *a; /* current arc pointers */ - bucket *l, *jL; /* bucket */ - long curDist, jD; - long state; - - - updateCnt ++; - - /* initialization */ - - forAllNodes(i) - i -> d = n; - sink -> d = 0; - - for (l = buckets; l <= buckets + dMax; l++) { - l -> firstActive = sentinelNode; - l -> firstInactive = sentinelNode; + node *i, *j; /* node pointers */ + arc *a; /* current arc pointers */ + bucket *l, *jL; /* bucket */ + long curDist, jD; + long state; + + + updateCnt ++; + + /* initialization */ + + forAllNodes(i) + i -> d = n; + sink -> d = 0; + + for (l = buckets; l <= buckets + dMax; l++) { + l -> firstActive = sentinelNode; + l -> firstInactive = sentinelNode; + } + + dMax = aMax = 0; + aMin = n; + + /* breadth first search */ + + // add sink to bucket zero + + iAdd(buckets, sink); + for (curDist = 0; 1; curDist++) { + + state = 0; + l = buckets + curDist; + jD = curDist + 1; + jL = l + 1; + /* + jL -> firstActive = sentinelNode; + jL -> firstInactive = sentinelNode; + */ + + if ((l->firstActive == sentinelNode) && + (l->firstInactive == sentinelNode)) + break; + + while (1) { + + switch (state) { + case 0: + i = l->firstInactive; + state = 1; + break; + case 1: + i = i->bNext; + break; + case 2: + i = l->firstActive; + state = 3; + break; + case 3: + i = i->bNext; + break; + default: + assert(0); + break; + } + + if (i == sentinelNode) { + if (state == 1) { + state = 2; + continue; } - - dMax = aMax = 0; - aMin = n; - - /* breadth first search */ - - // add sink to bucket zero - - iAdd(buckets, sink); - for (curDist = 0; 1; curDist++) { - - state = 0; - l = buckets + curDist; - jD = curDist + 1; - jL = l + 1; - /* - jL -> firstActive = sentinelNode; - jL -> firstInactive = sentinelNode; - */ - - if ((l->firstActive == sentinelNode) && - (l->firstInactive == sentinelNode)) - break; - - while (1) { - - switch (state) { - case 0: - i = l->firstInactive; - state = 1; - break; - case 1: - i = i->bNext; - break; - case 2: - i = l->firstActive; - state = 3; - break; - case 3: - i = i->bNext; - break; - default: - assert(0); - break; - } - - if (i == sentinelNode) { - if (state == 1) { - state = 2; - continue; - } - else { - assert(state == 3); - break; - } - } - - /* scanning arcs incident to node i */ - forAllArcs(i,a) { - if (a->rev->resCap > 0 ) { - j = a->head; - if (j->d == n) { - j->d = jD; - j->current = j->first; - if (jD > dMax) dMax = jD; - - if (j->excess > 0) { - /* put into active list */ - aAdd(jL,j); - } - else { - /* put into inactive list */ - iAdd(jL,j); - } - } - } - } /* node i is scanned */ - } + else { + assert(state == 3); + break; } + } + + /* scanning arcs incident to node i */ + forAllArcs(i,a) { + if (a->rev->resCap > 0 ) { + j = a->head; + if (j->d == n) { + j->d = jD; + j->current = j->first; + if (jD > dMax) dMax = jD; + + if (j->excess > 0) { + /* put into active list */ + aAdd(jL,j); + } + else { + /* put into inactive list */ + iAdd(jL,j); + } + } + } + } /* node i is scanned */ + } + } } /* end of global update */ void flow_solver::internal_check_max() { - bucket *l; + bucket *l; - for (l = buckets + dMax + 1; l < buckets + n; l++) { - assert(l->firstActive == sentinelNode); - assert(l->firstInactive == sentinelNode); - } + for (l = buckets + dMax + 1; l < buckets + n; l++) { + assert(l->firstActive == sentinelNode); + assert(l->firstInactive == sentinelNode); + } } void flow_solver::internal_init( ) { - node *i; /* current node */ - int overflowDetected; - bucket *l; - arc *a; + node *i; /* current node */ + int overflowDetected; + bucket *l; + arc *a; #ifdef EXCESS_TYPE_LONG - double testExcess; + double testExcess; #endif #ifndef OLD_INIT - unsigned long delta; + unsigned long delta; #endif - // initialize excesses + // initialize excesses - forAllNodes(i) { - i->excess = 0; - i->current = i->first; - forAllArcs(i, a) - a->resCap = cap[a-arcs]; - } + forAllNodes(i) { + i->excess = 0; + i->current = i->first; + forAllArcs(i, a) + a->resCap = cap[a-arcs]; + } - for (l = buckets; l <= buckets + n-1; l++) { - l -> firstActive = sentinelNode; - l -> firstInactive = sentinelNode; - } + for (l = buckets; l <= buckets + n-1; l++) { + l -> firstActive = sentinelNode; + l -> firstInactive = sentinelNode; + } - overflowDetected = 0; + overflowDetected = 0; #ifdef EXCESS_TYPE_LONG - testExcess = 0; - forAllArcs(source,a) { - if (a->head != source) { - testExcess += a->resCap; - } - } - if (testExcess > MAXLONG) { - printf("c WARNING: excess overflow. See README for details.\nc\n"); - overflowDetected = 1; - } + testExcess = 0; + forAllArcs(source,a) { + if (a->head != source) { + testExcess += a->resCap; + } + } + if (testExcess > MAXLONG) { + printf("c WARNING: excess overflow. See README for details.\nc\n"); + overflowDetected = 1; + } #endif #ifdef OLD_INIT - source -> excess = MAXLONG; + source -> excess = MAXLONG; #else - if (overflowDetected) { - source -> excess = MAXLONG; - } - else { - source->excess = 0; - forAllArcs(source,a) { - if (a->head != source) { - pushCnt ++; - delta = a -> resCap; - a -> resCap -= delta; - (a -> rev) -> resCap += delta; - a->head->excess += delta; - } - } - } - - /* setup labels and buckets */ - l = buckets + 1; - - aMax = 0; - aMin = n; - - forAllNodes(i) { - if (i == sink) { - i->d = 0; - iAdd(buckets,i); - continue; - } - if ((i == source) && (!overflowDetected)) { - i->d = n; - } - else - i->d = 1; - if (i->excess > 0) { - /* put into active list */ - aAdd(l,i); - } - else { /* i -> excess == 0 */ - /* put into inactive list */ - if (i->d < n) - iAdd(l,i); - } - } - dMax = 1; + if (overflowDetected) { + source -> excess = MAXLONG; + } + else { + source->excess = 0; + forAllArcs(source,a) { + if (a->head != source) { + pushCnt ++; + delta = a -> resCap; + a -> resCap -= delta; + (a -> rev) -> resCap += delta; + a->head->excess += delta; + } + } + } + + /* setup labels and buckets */ + l = buckets + 1; + + aMax = 0; + aMin = n; + + forAllNodes(i) { + if (i == sink) { + i->d = 0; + iAdd(buckets,i); + continue; + } + if ((i == source) && (!overflowDetected)) { + i->d = n; + } + else + i->d = 1; + if (i->excess > 0) { + /* put into active list */ + aAdd(l,i); + } + else { /* i -> excess == 0 */ + /* put into inactive list */ + if (i->d < n) + iAdd(l,i); + } + } + dMax = 1; #endif } /* end of init */ @@ -445,20 +444,20 @@ void flow_solver::internal_init( ) int flow_solver::internal_allocDS( ) { - nm = ALPHA * n + m; - /* - queue = (node**) calloc ( n, sizeof (node*) ); - if ( queue == NULL ) return ( 1 ); - qLast = queue + n - 1; - qInit(); - */ - buckets = (bucket*) calloc ( n+2, sizeof (bucket) ); - if ( buckets == NULL ) return ( 1 ); + nm = ALPHA * n + m; + /* + queue = (node**) calloc ( n, sizeof (node*) ); + if ( queue == NULL ) return ( 1 ); + qLast = queue + n - 1; + qInit(); + */ + buckets = (bucket*) calloc ( n+2, sizeof (bucket) ); + if ( buckets == NULL ) return ( 1 ); - sentinelNode = nodes + n; - sentinelNode->first = arcs + 2*m; + sentinelNode = nodes + n; + sentinelNode->first = arcs + 2*m; - return ( 0 ); + return ( 0 ); } /* end of allocate */ @@ -467,39 +466,39 @@ int flow_solver::internal_allocDS( ) int flow_solver::internal_gap (bucket* emptyB) { - bucket *l; - node *i; - long r; /* index of the bucket before l */ - int cc; /* cc = 1 if no nodes with positive excess before - the internal_gap */ - - gapCnt ++; - r = ( emptyB - buckets ) - 1; - - /* set labels of nodes beyond the internal_gap to "infinity" */ - for ( l = emptyB + 1; l <= buckets + dMax; l ++ ) { - /* this does nothing for high level selection - for (i = l -> firstActive; i != sentinelNode; i = i -> bNext) { - i -> d = n; - gNodeCnt++; - } - l -> firstActive = sentinelNode; - */ - - for ( i = l -> firstInactive; i != sentinelNode; i = i -> bNext ) { - i -> d = n; - gNodeCnt ++; - } + bucket *l; + node *i; + long r; /* index of the bucket before l */ + int cc; /* cc = 1 if no nodes with positive excess before + the internal_gap */ - l -> firstInactive = sentinelNode; - } + gapCnt ++; + r = ( emptyB - buckets ) - 1; + + /* set labels of nodes beyond the internal_gap to "infinity" */ + for ( l = emptyB + 1; l <= buckets + dMax; l ++ ) { + /* this does nothing for high level selection + for (i = l -> firstActive; i != sentinelNode; i = i -> bNext) { + i -> d = n; + gNodeCnt++; + } + l -> firstActive = sentinelNode; + */ + + for ( i = l -> firstInactive; i != sentinelNode; i = i -> bNext ) { + i -> d = n; + gNodeCnt ++; + } - cc = ( aMin > r ) ? 1 : 0; + l -> firstInactive = sentinelNode; + } - dMax = r; - aMax = r; + cc = ( aMin > r ) ? 1 : 0; - return ( cc ); + dMax = r; + aMax = r; + + return ( cc ); } @@ -508,43 +507,43 @@ int flow_solver::internal_gap (bucket* emptyB) long flow_solver::internal_relabel (node *i) { - node *j; - long minD; /* minimum d of a node reachable from i */ - arc *minA; /* an arc which leads to the node with minimal d */ - arc *a; + node *j; + long minD; /* minimum d of a node reachable from i */ + arc *minA; /* an arc which leads to the node with minimal d */ + arc *a; - assert(i->excess > 0); + assert(i->excess > 0); - relabelCnt++; - workSinceUpdate += BETA; + relabelCnt++; + workSinceUpdate += BETA; - i->d = minD = n; - minA = NULL; + i->d = minD = n; + minA = NULL; - /* find the minimum */ - forAllArcs(i,a) { - workSinceUpdate++; - if (a -> resCap > 0) { - j = a -> head; - if (j->d < minD) { - minD = j->d; - minA = a; - } - } - } + /* find the minimum */ + forAllArcs(i,a) { + workSinceUpdate++; + if (a -> resCap > 0) { + j = a -> head; + if (j->d < minD) { + minD = j->d; + minA = a; + } + } + } - minD++; + minD++; - if (minD < n) { + if (minD < n) { - i->d = minD; - i->current = minA; + i->d = minD; + i->current = minA; - if (dMax < minD) dMax = minD; + if (dMax < minD) dMax = minD; - } /* end of minD < n */ + } /* end of minD < n */ - return ( minD ); + return ( minD ); } /* end of internal_relabel */ @@ -554,93 +553,93 @@ long flow_solver::internal_relabel (node *i) void flow_solver::internal_discharge (node* i) { - node *j; /* sucsessor of i */ - long jD; /* d of the next bucket */ - bucket *lj; /* j's bucket */ - bucket *l; /* i's bucket */ - arc *a; /* current arc (i,j) */ - cType delta; - arc *stopA; - - assert(i->excess > 0); - assert(i != sink); - do { - - jD = i->d - 1; - l = buckets + i->d; - - /* scanning arcs outgoing from i */ - for (a = i->current, stopA = (i+1)->first; a != stopA; a++) { - if (a -> resCap > 0) { - j = a -> head; - - if (j->d == jD) { - pushCnt ++; - if (a->resCap < i->excess) - delta = a->resCap; - else - delta = i->excess; - a->resCap -= delta; - a->rev->resCap += delta; - - if (j != sink) { - - lj = buckets + jD; - - if (j->excess == 0) { - /* remove j from inactive list */ - iDelete(lj,j); - /* add j to active list */ - aAdd(lj,j); - } - } - - j -> excess += delta; - i -> excess -= delta; - - if (i->excess == 0) break; - - } /* j belongs to the next bucket */ - } /* a is not saturated */ - } /* end of scanning arcs from i */ - - if (a == stopA) { - /* i must be internal_relabeled */ - internal_relabel (i); - - if (i->d == n) break; - if ((l -> firstActive == sentinelNode) && - (l -> firstInactive == sentinelNode) - ) - internal_gap (l); - - if (i->d == n) break; - } - else { - /* i no longer active */ - i->current = a; - /* put i on inactive list */ - iAdd(l,i); - break; - } - } while (1); + node *j; /* sucsessor of i */ + long jD; /* d of the next bucket */ + bucket *lj; /* j's bucket */ + bucket *l; /* i's bucket */ + arc *a; /* current arc (i,j) */ + cType delta; + arc *stopA; + + assert(i->excess > 0); + assert(i != sink); + do { + + jD = i->d - 1; + l = buckets + i->d; + + /* scanning arcs outgoing from i */ + for (a = i->current, stopA = (i+1)->first; a != stopA; a++) { + if (a -> resCap > 0) { + j = a -> head; + + if (j->d == jD) { + pushCnt ++; + if (a->resCap < i->excess) + delta = a->resCap; + else + delta = i->excess; + a->resCap -= delta; + a->rev->resCap += delta; + + if (j != sink) { + + lj = buckets + jD; + + if (j->excess == 0) { + /* remove j from inactive list */ + iDelete(lj,j); + /* add j to active list */ + aAdd(lj,j); + } + } + + j -> excess += delta; + i -> excess -= delta; + + if (i->excess == 0) break; + + } /* j belongs to the next bucket */ + } /* a is not saturated */ + } /* end of scanning arcs from i */ + + if (a == stopA) { + /* i must be internal_relabeled */ + internal_relabel (i); + + if (i->d == n) break; + if ((l -> firstActive == sentinelNode) && + (l -> firstInactive == sentinelNode) + ) + internal_gap (l); + + if (i->d == n) break; + } + else { + /* i no longer active */ + i->current = a; + /* put i on inactive list */ + iAdd(l,i); + break; + } + } while (1); } // go from higher to lower buckets, push flow void flow_solver::internal_wave() { - node *i; - bucket *l; + node *i; + bucket *l; - for (l = buckets + aMax; l > buckets; l--) { - for (i = l->firstActive; i != sentinelNode; i = l->firstActive) { - aRemove(l,i); + for (l = buckets + aMax; l > buckets; l--) { + for (i = l->firstActive; i != sentinelNode; i = l->firstActive) { + aRemove(l,i); - assert(i->excess > 0); - internal_discharge (i); + assert(i->excess > 0); + internal_discharge (i); - } - } + } + } } - +} \ No newline at end of file diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/flow_solver.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/flow_solver.h index d2ffbd6e..518eebf4 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/flow_solver.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/flow_solver.h @@ -11,57 +11,57 @@ #include "data_structure/graph_access.h" #include "partition_config.h" #include "types.h" - +namespace kahip::modified { class flow_solver { - public: - flow_solver( ); - virtual ~flow_solver(); +public: + flow_solver( ); + virtual ~flow_solver(); - //************************************************************************************************* - //code copied from hi_pr. make this local variables so that we can run the flow code multiple times - //************************************************************************************************* - void internal_stage_one ( ); - void internal_stage_two ( ); + //************************************************************************************************* + //code copied from hi_pr. make this local variables so that we can run the flow code multiple times + //************************************************************************************************* + void internal_stage_one ( ); + void internal_stage_two ( ); - void internal_global_update(); - void internal_check_max(); - void internal_init( ); - int internal_allocDS( ); - void internal_wave(); - void internal_discharge(node* i); - long internal_relabel(node *i); - int internal_gap(bucket* emptyB); + void internal_global_update(); + void internal_check_max(); + void internal_init( ); + int internal_allocDS( ); + void internal_wave(); + void internal_discharge(node* i); + long internal_relabel(node *i); + int internal_gap(bucket* emptyB); - long n; /* number of nodes */ - long m; /* number of arcs */ - long nm; /* n + ALPHA * m */ - long nMin; /* smallest node id */ - node *nodes; /*[> array of nodes <]*/ - node *free_nodes; /*[> array of nodes <]*/ - arc *arcs; /* array of arcs */ - bucket *buckets; /* array of buckets */ - cType *cap; /* array of capacities */ - node *source; /* source node pointer */ - node *sink; /* sink node pointer */ - long dMax; /* maximum label */ - long aMax; /* maximum actie node label */ - long aMin; /* minimum active node label */ - double flow; /* flow value */ - long pushCnt; /* number of pushes */ - long relabelCnt; /* number of relabels */ - long updateCnt; /* number of updates */ - long gapCnt; /* number of gaps */ - long gNodeCnt; /* number of nodes after gap */ - float t, t2; /* for saving times */ - node *sentinelNode; /* end of the node list marker */ - arc *stopA; /* used in forAllArcs */ - long workSinceUpdate; /* the number of arc scans since last update */ - float globUpdtFreq; /* global update frequency */ + long n; /* number of nodes */ + long m; /* number of arcs */ + long nm; /* n + ALPHA * m */ + long nMin; /* smallest node id */ + node *nodes; /*[> array of nodes <]*/ + node *free_nodes; /*[> array of nodes <]*/ + arc *arcs; /* array of arcs */ + bucket *buckets; /* array of buckets */ + cType *cap; /* array of capacities */ + node *source; /* source node pointer */ + node *sink; /* sink node pointer */ + long dMax; /* maximum label */ + long aMax; /* maximum actie node label */ + long aMin; /* minimum active node label */ + double flow; /* flow value */ + long pushCnt; /* number of pushes */ + long relabelCnt; /* number of relabels */ + long updateCnt; /* number of updates */ + long gapCnt; /* number of gaps */ + long gNodeCnt; /* number of nodes after gap */ + float t, t2; /* for saving times */ + node *sentinelNode; /* end of the node list marker */ + arc *stopA; /* used in forAllArcs */ + long workSinceUpdate; /* the number of arc scans since last update */ + float globUpdtFreq; /* global update frequency */ - long i_dist; - node *i_next, *i_prev; + long i_dist; + node *i_next, *i_prev; }; - +} diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/timer.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/timer.cpp index 2f4bb9fa..65acb4b8 100755 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/timer.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/timer.cpp @@ -6,7 +6,7 @@ *****************************************************************************/ #include "timer.h" - +namespace kahip::modified { float timer () { struct rusage r; @@ -14,6 +14,6 @@ float timer () getrusage(0, &r); return (float)(r.ru_utime.tv_sec+r.ru_utime.tv_usec/(float)1000000); } - +} diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/timer.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/timer.h index b65b61db..0c1a2616 100755 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/timer.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/timer.h @@ -10,7 +10,7 @@ #include #include - +namespace kahip::modified { float timer (); - +} #endif diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/types.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/types.h index e36ac59a..169813ff 100755 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/types.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/types.h @@ -8,7 +8,7 @@ #ifndef TYPES_FIK18Y5X #define TYPES_FIK18Y5X - +namespace kahip::modified { #ifdef EXCESS_TYPE_LONG typedef unsigned long excessType; #else @@ -25,15 +25,15 @@ typedef /* arc */ struct nodeSt *head; /* arc head */ struct arcSt *rev; /* reverse arc */ } - arc; +arc; typedef /* node */ struct nodeSt { arc *first; /* first outgoing arc */ arc *current; /* current outgoing arc */ - excessType excess; /* excess at the node - change to double if needed */ + excessType excess; /* excess at the node + change to double if needed */ long d; /* distance label */ struct nodeSt *bNext; /* next node in bucket */ struct nodeSt *bPrev; /* previous node in bucket */ @@ -43,8 +43,8 @@ typedef /* node */ typedef /* bucket */ struct bucketSt { - node *firstActive; /* first node with positive excess */ - node *firstInactive; /* first node with zero excess */ + node *firstActive; /* first node with positive excess */ + node *firstInactive; /* first node with zero excess */ } bucket; - +} #endif /* end of include guard: TYPES_FIK18Y5X */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/most_balanced_minimum_cuts/most_balanced_minimum_cuts.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/most_balanced_minimum_cuts/most_balanced_minimum_cuts.cpp index 34d12366..f476c7c0 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/most_balanced_minimum_cuts/most_balanced_minimum_cuts.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/most_balanced_minimum_cuts/most_balanced_minimum_cuts.cpp @@ -10,7 +10,7 @@ #include "algorithms/strongly_connected_components.h" #include "algorithms/topological_sort.h" #include "most_balanced_minimum_cuts.h" - +namespace kahip::modified { most_balanced_minimum_cuts::most_balanced_minimum_cuts() { } @@ -24,147 +24,147 @@ void most_balanced_minimum_cuts::compute_good_balanced_min_cut( graph_access & r NodeWeight & perfect_rhs_weight, std::vector< NodeID > & new_rhs_nodes ) { - strongly_connected_components scc; - std::vector components(residualGraph.number_of_nodes()); - int comp_count = scc.strong_components(residualGraph,components); - - std::vector< std::vector > comp_nodes(comp_count); - std::vector< NodeWeight > comp_weights(comp_count, 0); - - forall_nodes(residualGraph, node) { - comp_nodes[components[node]].push_back(node); - comp_weights[components[node]] += residualGraph.getNodeWeight(node); - } endfor - - NodeID s = residualGraph.number_of_nodes()-2; - NodeID t = residualGraph.number_of_nodes()-1; - int comp_of_s = components[s]; - int comp_of_t = components[t]; - - graph_access scc_graph; - build_internal_scc_graph( residualGraph, components, comp_count, scc_graph); - - std::vector comp_for_rhs; - compute_new_rhs(scc_graph, config, comp_weights, comp_of_s, comp_of_t, perfect_rhs_weight, comp_for_rhs); - - //add comp_for_rhs nodes to new rhs - for( unsigned i = 0; i < comp_for_rhs.size(); i++) { - int cur_component = comp_for_rhs[i]; - if(cur_component != comp_of_s && cur_component != comp_of_t) { - for( unsigned j = 0; j < comp_nodes[cur_component].size(); j++) { - new_rhs_nodes.push_back(comp_nodes[cur_component][j]); - } - } - } + strongly_connected_components scc; + std::vector components(residualGraph.number_of_nodes()); + int comp_count = scc.strong_components(residualGraph,components); + + std::vector< std::vector > comp_nodes(comp_count); + std::vector< NodeWeight > comp_weights(comp_count, 0); + + forall_nodes(residualGraph, node) { + comp_nodes[components[node]].push_back(node); + comp_weights[components[node]] += residualGraph.getNodeWeight(node); + } endfor + + NodeID s = residualGraph.number_of_nodes()-2; + NodeID t = residualGraph.number_of_nodes()-1; + int comp_of_s = components[s]; + int comp_of_t = components[t]; + + graph_access scc_graph; + build_internal_scc_graph( residualGraph, components, comp_count, scc_graph); + + std::vector comp_for_rhs; + compute_new_rhs(scc_graph, config, comp_weights, comp_of_s, comp_of_t, perfect_rhs_weight, comp_for_rhs); + + //add comp_for_rhs nodes to new rhs + for( unsigned i = 0; i < comp_for_rhs.size(); i++) { + int cur_component = comp_for_rhs[i]; + if(cur_component != comp_of_s && cur_component != comp_of_t) { + for( unsigned j = 0; j < comp_nodes[cur_component].size(); j++) { + new_rhs_nodes.push_back(comp_nodes[cur_component][j]); + } + } + } } -void most_balanced_minimum_cuts::compute_new_rhs( graph_access & scc_graph, +void most_balanced_minimum_cuts::compute_new_rhs( graph_access & scc_graph, const PartitionConfig & config, std::vector< NodeWeight > & comp_weights, int comp_of_s, int comp_of_t, NodeWeight optimal_rhs_stripe_weight, std::vector & comp_for_rhs) { - - //all successors of s cant be in any closure so they are marked invalid / this is basically a bfs in a DAG - std::vector valid_to_add(scc_graph.number_of_nodes(), true); - std::queue node_queue; - node_queue.push(comp_of_s); - valid_to_add[comp_of_s] = false; - - while (!node_queue.empty()) { - NodeID node = node_queue.front(); - node_queue.pop(); - forall_out_edges(scc_graph, e, node) { - NodeID target = scc_graph.getEdgeTarget(e); - if(valid_to_add[target] == true) { - valid_to_add[target] = false; - node_queue.push(target); - } - } endfor - } - std::vector tmp_comp_for_rhs; - int best_diff = std::numeric_limits::max(); - for(unsigned i = 0; i < config.toposort_iterations; i++) { - topological_sort ts; - std::vector sorted_sequence; - ts.sort(scc_graph, sorted_sequence); - - tmp_comp_for_rhs.clear(); - - bool t_contained = false; - int cur_rhs_weight = 0; - int diff = 0; - for( unsigned idx = 0; idx < sorted_sequence.size(); idx++) { - int cur_component = sorted_sequence[idx]; - - if( cur_component == comp_of_t ) { - t_contained = true; - } - - if(valid_to_add[cur_component]) { - int tmpdiff = optimal_rhs_stripe_weight - cur_rhs_weight - comp_weights[cur_component]; - bool would_break = tmpdiff <= 0 && t_contained; - if(!would_break) { - tmp_comp_for_rhs.push_back(cur_component); - cur_rhs_weight += comp_weights[cur_component]; - } else { - //decide wether we should add this component now - if(abs(tmpdiff) < abs(diff)) { - //the add it - tmp_comp_for_rhs.push_back(cur_component); - cur_rhs_weight += comp_weights[cur_component]; - diff = optimal_rhs_stripe_weight - cur_rhs_weight; - } - break; - } - - } - - diff = optimal_rhs_stripe_weight - cur_rhs_weight; - if( diff <= 0 && t_contained) { - break; - } - - } - if(abs(diff) < best_diff) { - best_diff = abs(diff); - comp_for_rhs = tmp_comp_for_rhs; - } + //all successors of s cant be in any closure so they are marked invalid / this is basically a bfs in a DAG + std::vector valid_to_add(scc_graph.number_of_nodes(), true); + std::queue node_queue; + node_queue.push(comp_of_s); + valid_to_add[comp_of_s] = false; + + while (!node_queue.empty()) { + NodeID node = node_queue.front(); + node_queue.pop(); + forall_out_edges(scc_graph, e, node) { + NodeID target = scc_graph.getEdgeTarget(e); + if(valid_to_add[target] == true) { + valid_to_add[target] = false; + node_queue.push(target); + } + } endfor +} + std::vector tmp_comp_for_rhs; + int best_diff = std::numeric_limits::max(); + for(unsigned i = 0; i < config.toposort_iterations; i++) { + topological_sort ts; + std::vector sorted_sequence; + ts.sort(scc_graph, sorted_sequence); + + tmp_comp_for_rhs.clear(); + + bool t_contained = false; + int cur_rhs_weight = 0; + int diff = 0; + for( unsigned idx = 0; idx < sorted_sequence.size(); idx++) { + int cur_component = sorted_sequence[idx]; + + if( cur_component == comp_of_t ) { + t_contained = true; + } + + if(valid_to_add[cur_component]) { + int tmpdiff = optimal_rhs_stripe_weight - cur_rhs_weight - comp_weights[cur_component]; + bool would_break = tmpdiff <= 0 && t_contained; + if(!would_break) { + tmp_comp_for_rhs.push_back(cur_component); + cur_rhs_weight += comp_weights[cur_component]; + } else { + //decide wether we should add this component now + if(abs(tmpdiff) < abs(diff)) { + //the add it + tmp_comp_for_rhs.push_back(cur_component); + cur_rhs_weight += comp_weights[cur_component]; + diff = optimal_rhs_stripe_weight - cur_rhs_weight; + } + break; } + + } + + diff = optimal_rhs_stripe_weight - cur_rhs_weight; + if( diff <= 0 && t_contained) { + break; + } + + } + if(abs(diff) < best_diff) { + best_diff = abs(diff); + comp_for_rhs = tmp_comp_for_rhs; + } + + } } -void most_balanced_minimum_cuts::build_internal_scc_graph( graph_access & residualGraph, - std::vector & components, - int comp_count, +void most_balanced_minimum_cuts::build_internal_scc_graph( graph_access & residualGraph, + std::vector & components, + int comp_count, graph_access & scc_graph) { - std::vector< std::vector > edges(comp_count); - unsigned edge_count = 0; - forall_nodes(residualGraph, node) { - forall_out_edges(residualGraph, e, node) { - NodeID target = residualGraph.getEdgeTarget(e); - if(components[node] != components[target]) { - edges[components[node]].push_back(components[target]); - edge_count++; - } - } endfor - } endfor - - //build_scc_graph - scc_graph.start_construction(comp_count, edge_count); - for( unsigned i = 0; i < (unsigned) comp_count; i++) { - NodeID node = scc_graph.new_node(); - std::unordered_map allready_contained; - for(unsigned j = 0; j < edges[i].size(); j++) { - if(allready_contained.find(edges[i][j]) == allready_contained.end()) { - scc_graph.new_edge(node, edges[i][j]); - allready_contained[edges[i][j]] = true; - } - } - } - - scc_graph.finish_construction(); + std::vector< std::vector > edges(comp_count); + unsigned edge_count = 0; + forall_nodes(residualGraph, node) { + forall_out_edges(residualGraph, e, node) { + NodeID target = residualGraph.getEdgeTarget(e); + if(components[node] != components[target]) { + edges[components[node]].push_back(components[target]); + edge_count++; + } + } endfor +} endfor + +//build_scc_graph +scc_graph.start_construction(comp_count, edge_count); + for( unsigned i = 0; i < (unsigned) comp_count; i++) { + NodeID node = scc_graph.new_node(); + std::unordered_map allready_contained; + for(unsigned j = 0; j < edges[i].size(); j++) { + if(allready_contained.find(edges[i][j]) == allready_contained.end()) { + scc_graph.new_edge(node, edges[i][j]); + allready_contained[edges[i][j]] = true; + } + } + } + + scc_graph.finish_construction(); +} } - diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/most_balanced_minimum_cuts/most_balanced_minimum_cuts.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/most_balanced_minimum_cuts/most_balanced_minimum_cuts.h index 3b82bc95..ad05b64c 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/most_balanced_minimum_cuts/most_balanced_minimum_cuts.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/most_balanced_minimum_cuts/most_balanced_minimum_cuts.h @@ -10,31 +10,31 @@ #include "data_structure/graph_access.h" #include "partition_config.h" - +namespace kahip::modified { class most_balanced_minimum_cuts { - public: - most_balanced_minimum_cuts(); - virtual ~most_balanced_minimum_cuts(); - - void compute_good_balanced_min_cut( graph_access & residualGraph, - const PartitionConfig & config, - NodeWeight & perfect_rhs_weight, - std::vector< NodeID > & new_rhs_node ); +public: + most_balanced_minimum_cuts(); + virtual ~most_balanced_minimum_cuts(); - private: - void build_internal_scc_graph( graph_access & residualGraph, - std::vector & components, - int comp_count, - graph_access & scc_graph); - - void compute_new_rhs( graph_access & scc_graph, + void compute_good_balanced_min_cut( graph_access & residualGraph, const PartitionConfig & config, - std::vector< NodeWeight > & comp_weights, - int comp_of_s, - int comp_of_t, - NodeWeight optimal_rhs_weight, - std::vector & comp_for_rhs); + NodeWeight & perfect_rhs_weight, + std::vector< NodeID > & new_rhs_node ); + +private: + void build_internal_scc_graph( graph_access & residualGraph, + std::vector & components, + int comp_count, + graph_access & scc_graph); + + void compute_new_rhs( graph_access & scc_graph, + const PartitionConfig & config, + std::vector< NodeWeight > & comp_weights, + int comp_of_s, + int comp_of_t, + NodeWeight optimal_rhs_weight, + std::vector & comp_for_rhs); }; - +} #endif /* end of include guard: MOST_BALANCED_MINIMUM_CUTS_SBD5CS */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/two_way_flow_refinement.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/two_way_flow_refinement.cpp index c796d3ef..0177a5d6 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/two_way_flow_refinement.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/two_way_flow_refinement.cpp @@ -15,7 +15,7 @@ #include "flow_solving_kernel/edge_cut_flow_solver.h" #include "quality_metrics.h" #include "two_way_flow_refinement.h" - +namespace kahip::modified { two_way_flow_refinement::two_way_flow_refinement() { } @@ -35,258 +35,258 @@ EdgeWeight two_way_flow_refinement::perform_refinement(PartitionConfig & config, EdgeWeight & cut, bool & something_changed) { - EdgeWeight retval = iterativ_flow_iteration(config, G, boundary, lhs_pq_start_nodes, rhs_pq_start_nodes, - refinement_pair, lhs_part_weight, rhs_part_weight, cut, something_changed); + EdgeWeight retval = iterativ_flow_iteration(config, G, boundary, lhs_pq_start_nodes, rhs_pq_start_nodes, + refinement_pair, lhs_part_weight, rhs_part_weight, cut, something_changed); - if(retval > 0) { - something_changed = true; - } + if(retval > 0) { + something_changed = true; + } - return retval; + return retval; } -EdgeWeight two_way_flow_refinement::iterativ_flow_iteration(PartitionConfig & config, +EdgeWeight two_way_flow_refinement::iterativ_flow_iteration(PartitionConfig & config, graph_access & G, - complete_boundary & boundary, - std::vector & lhs_pq_start_nodes, + complete_boundary & boundary, + std::vector & lhs_pq_start_nodes, std::vector & rhs_pq_start_nodes, - boundary_pair * refinement_pair, + boundary_pair * refinement_pair, NodeWeight & lhs_part_weight, NodeWeight & rhs_part_weight, EdgeWeight & cut, bool & something_changed) { - if(lhs_pq_start_nodes.size() == 0 or rhs_pq_start_nodes.size() == 0) return 0; // nothing to refine - ASSERT_TRUE(lhs_part_weight < config.upper_bound_partition && rhs_part_weight < config.upper_bound_partition); - - PartitionID lhs = refinement_pair->lhs; - PartitionID rhs = refinement_pair->rhs; - boundary_bfs bfs_region_searcher; - - double region_factor = config.flow_region_factor; - unsigned max_iterations = config.max_flow_iterations; - unsigned iteration = 0; - - std::vector lhs_nodes; - std::vector rhs_nodes; - - EdgeWeight cur_improvement = 1; - EdgeWeight best_cut = cut; - bool sumoverweight = lhs_part_weight + rhs_part_weight > 2*config.upper_bound_partition; - if(sumoverweight) { - return 0; - } - - NodeWeight average_partition_weight = ceil(config.largest_graph_weight / config.k); - while(cur_improvement > 0 && iteration < max_iterations) { - NodeWeight upper_bound_no_lhs = (NodeWeight)std::max((100.0+region_factor*config.imbalance)/100.0*(average_partition_weight) - rhs_part_weight,0.0); - NodeWeight upper_bound_no_rhs = (NodeWeight)std::max((100.0+region_factor*config.imbalance)/100.0*(average_partition_weight) - lhs_part_weight,0.0); - - std::vector lhs_boundary_stripe; - NodeWeight lhs_stripe_weight = 0; - if(!bfs_region_searcher.boundary_bfs_search(G, lhs_pq_start_nodes, lhs, - upper_bound_no_lhs, lhs_boundary_stripe, - lhs_stripe_weight, true)) { - - EdgeWeight improvement = cut-best_cut; - cut = best_cut; - return improvement; - } - - - std::vector rhs_boundary_stripe; - NodeWeight rhs_stripe_weight = 0; - if(!bfs_region_searcher.boundary_bfs_search(G, rhs_pq_start_nodes, rhs, - upper_bound_no_rhs, rhs_boundary_stripe, - rhs_stripe_weight, true)) { - - EdgeWeight improvement = cut-best_cut; - cut = best_cut; - return improvement; - } - - std::vector new_rhs_nodes; - std::vector new_to_old_ids; - - edge_cut_flow_solver fsolve; - EdgeWeight new_cut = fsolve.get_min_flow_max_cut(config, G, - lhs, rhs, - lhs_boundary_stripe, rhs_boundary_stripe, - new_to_old_ids, cut, - rhs_part_weight, - rhs_stripe_weight, - new_rhs_nodes); - - NodeWeight new_lhs_part_weight = 0; - NodeWeight new_rhs_part_weight = 0; - NodeWeight new_lhs_stripe_weight = 0; - NodeWeight new_rhs_stripe_weight = 0; - NodeID no_nodes_flow_graph = lhs_boundary_stripe.size() + rhs_boundary_stripe.size(); - - for(unsigned i = 0; i < new_rhs_nodes.size(); i++) { - NodeID new_rhs_node = new_rhs_nodes[i]; - if(new_rhs_node < no_nodes_flow_graph) { // not target and source - NodeID old_node_id = new_to_old_ids[new_rhs_node]; - new_rhs_stripe_weight += G.getNodeWeight(old_node_id); - G.setPartitionIndex(old_node_id, BOUNDARY_STRIPE_NODE-1); - } - } - - for(unsigned i = 0; i < lhs_boundary_stripe.size(); i++) { - if( G.getPartitionIndex(lhs_boundary_stripe[i]) == BOUNDARY_STRIPE_NODE) { - new_lhs_stripe_weight += G.getNodeWeight(lhs_boundary_stripe[i]); - } - } - - for(unsigned i = 0; i < rhs_boundary_stripe.size(); i++) { - if( G.getPartitionIndex(rhs_boundary_stripe[i]) == BOUNDARY_STRIPE_NODE) { - new_lhs_stripe_weight += G.getNodeWeight(rhs_boundary_stripe[i]); - } - } - new_lhs_part_weight = boundary.getBlockWeight(lhs) + ((int)new_lhs_stripe_weight-(int)lhs_stripe_weight) ; - new_rhs_part_weight = boundary.getBlockWeight(rhs) + ((int)new_rhs_stripe_weight-(int)rhs_stripe_weight) ; - - bool partition_is_feasable = false; - if(config.most_balanced_minimum_cuts) { - partition_is_feasable = new_lhs_part_weight < config.upper_bound_partition - && new_rhs_part_weight < config.upper_bound_partition - && (new_cut < best_cut || abs((int)new_lhs_part_weight - (int) new_rhs_part_weight) < abs((int)lhs_part_weight - (int)rhs_part_weight)); - } else { - partition_is_feasable = new_lhs_part_weight < config.upper_bound_partition - && new_rhs_part_weight < config.upper_bound_partition && new_cut < best_cut; - } - - if(partition_is_feasable) { - // then this partition can be accepted - apply_partition_and_update_boundary( config, G, refinement_pair, - lhs, rhs, boundary, - lhs_boundary_stripe, rhs_boundary_stripe, - lhs_stripe_weight, - rhs_stripe_weight, - new_to_old_ids, - new_rhs_nodes); - - boundary.setEdgeCut(refinement_pair, new_cut); - - lhs_part_weight = boundary.getBlockWeight(lhs); - rhs_part_weight = boundary.getBlockWeight(rhs); - ASSERT_TRUE(lhs_part_weight < config.upper_bound_partition && rhs_part_weight < config.upper_bound_partition); - - - cur_improvement = best_cut - new_cut; - best_cut = new_cut; - - if(2*region_factor < config.flow_region_factor) { - region_factor *= 2; - } else { - region_factor = config.flow_region_factor; - } - if(region_factor == config.flow_region_factor) { - //in that case we are finished - break; - } - if( iteration+1 < max_iterations ) { - // update the start nodes for the bfs - lhs_pq_start_nodes.clear(); - boundary.setup_start_nodes(G, lhs, *refinement_pair, lhs_pq_start_nodes); - - rhs_pq_start_nodes.clear(); - boundary.setup_start_nodes(G, rhs, *refinement_pair, rhs_pq_start_nodes); - } - - - } else { - //undo changes - for( unsigned i = 0; i < lhs_boundary_stripe.size(); i++) { - G.setPartitionIndex(lhs_boundary_stripe[i], lhs); - } - for( unsigned i = 0; i < rhs_boundary_stripe.size(); i++) { - G.setPartitionIndex(rhs_boundary_stripe[i], rhs); - } - - //smaller the region_factor - region_factor = std::max(region_factor/2,1.0); - if(new_cut == best_cut) { - break; - } - } - iteration++; - } - - - ASSERT_TRUE(lhs_part_weight < config.upper_bound_partition && rhs_part_weight < config.upper_bound_partition); - EdgeWeight improvement = cut-best_cut; - cut = best_cut; - return improvement; + if(lhs_pq_start_nodes.size() == 0 or rhs_pq_start_nodes.size() == 0) return 0; // nothing to refine + ASSERT_TRUE(lhs_part_weight < config.upper_bound_partition && rhs_part_weight < config.upper_bound_partition); + + PartitionID lhs = refinement_pair->lhs; + PartitionID rhs = refinement_pair->rhs; + boundary_bfs bfs_region_searcher; + + double region_factor = config.flow_region_factor; + unsigned max_iterations = config.max_flow_iterations; + unsigned iteration = 0; + + std::vector lhs_nodes; + std::vector rhs_nodes; + + EdgeWeight cur_improvement = 1; + EdgeWeight best_cut = cut; + bool sumoverweight = lhs_part_weight + rhs_part_weight > 2*config.upper_bound_partition; + if(sumoverweight) { + return 0; + } + + NodeWeight average_partition_weight = ceil(config.largest_graph_weight / config.k); + while(cur_improvement > 0 && iteration < max_iterations) { + NodeWeight upper_bound_no_lhs = (NodeWeight)std::max((100.0+region_factor*config.imbalance)/100.0*(average_partition_weight) - rhs_part_weight,0.0); + NodeWeight upper_bound_no_rhs = (NodeWeight)std::max((100.0+region_factor*config.imbalance)/100.0*(average_partition_weight) - lhs_part_weight,0.0); + + std::vector lhs_boundary_stripe; + NodeWeight lhs_stripe_weight = 0; + if(!bfs_region_searcher.boundary_bfs_search(G, lhs_pq_start_nodes, lhs, + upper_bound_no_lhs, lhs_boundary_stripe, + lhs_stripe_weight, true)) { + + EdgeWeight improvement = cut-best_cut; + cut = best_cut; + return improvement; + } + + + std::vector rhs_boundary_stripe; + NodeWeight rhs_stripe_weight = 0; + if(!bfs_region_searcher.boundary_bfs_search(G, rhs_pq_start_nodes, rhs, + upper_bound_no_rhs, rhs_boundary_stripe, + rhs_stripe_weight, true)) { + + EdgeWeight improvement = cut-best_cut; + cut = best_cut; + return improvement; + } + + std::vector new_rhs_nodes; + std::vector new_to_old_ids; + + edge_cut_flow_solver fsolve; + EdgeWeight new_cut = fsolve.get_min_flow_max_cut(config, G, + lhs, rhs, + lhs_boundary_stripe, rhs_boundary_stripe, + new_to_old_ids, cut, + rhs_part_weight, + rhs_stripe_weight, + new_rhs_nodes); + + NodeWeight new_lhs_part_weight = 0; + NodeWeight new_rhs_part_weight = 0; + NodeWeight new_lhs_stripe_weight = 0; + NodeWeight new_rhs_stripe_weight = 0; + NodeID no_nodes_flow_graph = lhs_boundary_stripe.size() + rhs_boundary_stripe.size(); + + for(unsigned i = 0; i < new_rhs_nodes.size(); i++) { + NodeID new_rhs_node = new_rhs_nodes[i]; + if(new_rhs_node < no_nodes_flow_graph) { // not target and source + NodeID old_node_id = new_to_old_ids[new_rhs_node]; + new_rhs_stripe_weight += G.getNodeWeight(old_node_id); + G.setPartitionIndex(old_node_id, BOUNDARY_STRIPE_NODE-1); + } + } + + for(unsigned i = 0; i < lhs_boundary_stripe.size(); i++) { + if( G.getPartitionIndex(lhs_boundary_stripe[i]) == BOUNDARY_STRIPE_NODE) { + new_lhs_stripe_weight += G.getNodeWeight(lhs_boundary_stripe[i]); + } + } + + for(unsigned i = 0; i < rhs_boundary_stripe.size(); i++) { + if( G.getPartitionIndex(rhs_boundary_stripe[i]) == BOUNDARY_STRIPE_NODE) { + new_lhs_stripe_weight += G.getNodeWeight(rhs_boundary_stripe[i]); + } + } + new_lhs_part_weight = boundary.getBlockWeight(lhs) + ((int)new_lhs_stripe_weight-(int)lhs_stripe_weight) ; + new_rhs_part_weight = boundary.getBlockWeight(rhs) + ((int)new_rhs_stripe_weight-(int)rhs_stripe_weight) ; + + bool partition_is_feasable = false; + if(config.most_balanced_minimum_cuts) { + partition_is_feasable = new_lhs_part_weight < config.upper_bound_partition + && new_rhs_part_weight < config.upper_bound_partition + && (new_cut < best_cut || abs((int)new_lhs_part_weight - (int) new_rhs_part_weight) < abs((int)lhs_part_weight - (int)rhs_part_weight)); + } else { + partition_is_feasable = new_lhs_part_weight < config.upper_bound_partition + && new_rhs_part_weight < config.upper_bound_partition && new_cut < best_cut; + } + + if(partition_is_feasable) { + // then this partition can be accepted + apply_partition_and_update_boundary( config, G, refinement_pair, + lhs, rhs, boundary, + lhs_boundary_stripe, rhs_boundary_stripe, + lhs_stripe_weight, + rhs_stripe_weight, + new_to_old_ids, + new_rhs_nodes); + + boundary.setEdgeCut(refinement_pair, new_cut); + + lhs_part_weight = boundary.getBlockWeight(lhs); + rhs_part_weight = boundary.getBlockWeight(rhs); + ASSERT_TRUE(lhs_part_weight < config.upper_bound_partition && rhs_part_weight < config.upper_bound_partition); + + + cur_improvement = best_cut - new_cut; + best_cut = new_cut; + + if(2*region_factor < config.flow_region_factor) { + region_factor *= 2; + } else { + region_factor = config.flow_region_factor; + } + if(region_factor == config.flow_region_factor) { + //in that case we are finished + break; + } + if( iteration+1 < max_iterations ) { + // update the start nodes for the bfs + lhs_pq_start_nodes.clear(); + boundary.setup_start_nodes(G, lhs, *refinement_pair, lhs_pq_start_nodes); + + rhs_pq_start_nodes.clear(); + boundary.setup_start_nodes(G, rhs, *refinement_pair, rhs_pq_start_nodes); + } + + + } else { + //undo changes + for( unsigned i = 0; i < lhs_boundary_stripe.size(); i++) { + G.setPartitionIndex(lhs_boundary_stripe[i], lhs); + } + for( unsigned i = 0; i < rhs_boundary_stripe.size(); i++) { + G.setPartitionIndex(rhs_boundary_stripe[i], rhs); + } + + //smaller the region_factor + region_factor = std::max(region_factor/2,1.0); + if(new_cut == best_cut) { + break; + } + } + iteration++; + } + + + ASSERT_TRUE(lhs_part_weight < config.upper_bound_partition && rhs_part_weight < config.upper_bound_partition); + EdgeWeight improvement = cut-best_cut; + cut = best_cut; + return improvement; } -void two_way_flow_refinement::apply_partition_and_update_boundary( const PartitionConfig & config, - graph_access & G, +void two_way_flow_refinement::apply_partition_and_update_boundary( const PartitionConfig & config, + graph_access & G, boundary_pair * refinement_pair, - PartitionID & lhs, + PartitionID & lhs, PartitionID & rhs, - complete_boundary & boundary, + complete_boundary & boundary, std::vector & lhs_boundary_stripe, std::vector & rhs_boundary_stripe, - NodeWeight & lhs_stripe_weight, - NodeWeight & rhs_stripe_weight, + NodeWeight & lhs_stripe_weight, + NodeWeight & rhs_stripe_weight, std::vector & new_to_old_ids, std::vector & new_rhs_nodes) { - NodeID no_nodes_flow_graph = lhs_boundary_stripe.size() + rhs_boundary_stripe.size(); - NodeWeight new_lhs_stripe_weight = 0; - NodeWeight new_rhs_stripe_weight = 0; - - NodeWeight new_rhs_stripe_no_nodes = 0; - NodeWeight new_lhs_stripe_no_nodes = 0; + NodeID no_nodes_flow_graph = lhs_boundary_stripe.size() + rhs_boundary_stripe.size(); + NodeWeight new_lhs_stripe_weight = 0; + NodeWeight new_rhs_stripe_weight = 0; - for(unsigned i = 0; i < new_rhs_nodes.size(); i++) { - NodeID new_rhs_node = new_rhs_nodes[i]; - if(new_rhs_node < no_nodes_flow_graph) { // not target and source - NodeID old_node_id = new_to_old_ids[new_rhs_node]; - G.setPartitionIndex(old_node_id, rhs); - new_rhs_stripe_weight += G.getNodeWeight(old_node_id); - new_rhs_stripe_no_nodes++; - } - } + NodeWeight new_rhs_stripe_no_nodes = 0; + NodeWeight new_lhs_stripe_no_nodes = 0; - for(unsigned i = 0; i < lhs_boundary_stripe.size(); i++) { - if( G.getPartitionIndex(lhs_boundary_stripe[i]) == BOUNDARY_STRIPE_NODE) { - G.setPartitionIndex(lhs_boundary_stripe[i], lhs); - new_lhs_stripe_weight += G.getNodeWeight(lhs_boundary_stripe[i]); - new_lhs_stripe_no_nodes++; - } - } + for(unsigned i = 0; i < new_rhs_nodes.size(); i++) { + NodeID new_rhs_node = new_rhs_nodes[i]; + if(new_rhs_node < no_nodes_flow_graph) { // not target and source + NodeID old_node_id = new_to_old_ids[new_rhs_node]; + G.setPartitionIndex(old_node_id, rhs); + new_rhs_stripe_weight += G.getNodeWeight(old_node_id); + new_rhs_stripe_no_nodes++; + } + } - for(unsigned i = 0; i < rhs_boundary_stripe.size(); i++) { - if( G.getPartitionIndex(rhs_boundary_stripe[i]) == BOUNDARY_STRIPE_NODE) { - G.setPartitionIndex(rhs_boundary_stripe[i], lhs); - new_lhs_stripe_weight += G.getNodeWeight(rhs_boundary_stripe[i]); - new_lhs_stripe_no_nodes++; - } - } + for(unsigned i = 0; i < lhs_boundary_stripe.size(); i++) { + if( G.getPartitionIndex(lhs_boundary_stripe[i]) == BOUNDARY_STRIPE_NODE) { + G.setPartitionIndex(lhs_boundary_stripe[i], lhs); + new_lhs_stripe_weight += G.getNodeWeight(lhs_boundary_stripe[i]); + new_lhs_stripe_no_nodes++; + } + } + for(unsigned i = 0; i < rhs_boundary_stripe.size(); i++) { + if( G.getPartitionIndex(rhs_boundary_stripe[i]) == BOUNDARY_STRIPE_NODE) { + G.setPartitionIndex(rhs_boundary_stripe[i], lhs); + new_lhs_stripe_weight += G.getNodeWeight(rhs_boundary_stripe[i]); + new_lhs_stripe_no_nodes++; + } + } - // ********** fix the boundary data structure ***************** - boundary.setBlockWeight(lhs, boundary.getBlockWeight(lhs) + ((int)new_lhs_stripe_weight-(int)lhs_stripe_weight) ); - boundary.setBlockWeight(rhs, boundary.getBlockWeight(rhs) + ((int)new_rhs_stripe_weight-(int)rhs_stripe_weight) ); - boundary.setBlockNoNodes(lhs, boundary.getBlockNoNodes(lhs) + ((int)new_lhs_stripe_no_nodes -(int)lhs_boundary_stripe.size()) ); - boundary.setBlockNoNodes(rhs, boundary.getBlockNoNodes(rhs) + ((int)new_rhs_stripe_no_nodes -(int)rhs_boundary_stripe.size()) ); + // ********** fix the boundary data structure ***************** + boundary.setBlockWeight(lhs, boundary.getBlockWeight(lhs) + ((int)new_lhs_stripe_weight-(int)lhs_stripe_weight) ); + boundary.setBlockWeight(rhs, boundary.getBlockWeight(rhs) + ((int)new_rhs_stripe_weight-(int)rhs_stripe_weight) ); + boundary.setBlockNoNodes(lhs, boundary.getBlockNoNodes(lhs) + ((int)new_lhs_stripe_no_nodes -(int)lhs_boundary_stripe.size()) ); + boundary.setBlockNoNodes(rhs, boundary.getBlockNoNodes(rhs) + ((int)new_rhs_stripe_no_nodes -(int)rhs_boundary_stripe.size()) ); - //this can be improved by only calling this method on the nodes that changed the partition - for(unsigned i = 0; i < lhs_boundary_stripe.size(); i++) { - boundary.postMovedBoundaryNodeUpdates(lhs_boundary_stripe[i], refinement_pair, false, true); - } - for(unsigned i = 0; i < rhs_boundary_stripe.size(); i++) { - boundary.postMovedBoundaryNodeUpdates(rhs_boundary_stripe[i], refinement_pair, false, true); - } + //this can be improved by only calling this method on the nodes that changed the partition + for(unsigned i = 0; i < lhs_boundary_stripe.size(); i++) { + boundary.postMovedBoundaryNodeUpdates(lhs_boundary_stripe[i], refinement_pair, false, true); + } -} + for(unsigned i = 0; i < rhs_boundary_stripe.size(); i++) { + boundary.postMovedBoundaryNodeUpdates(rhs_boundary_stripe[i], refinement_pair, false, true); + } +} +} diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/two_way_flow_refinement.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/two_way_flow_refinement.h index 65fe4506..d75b398f 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/two_way_flow_refinement.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement/two_way_flow_refinement.h @@ -14,49 +14,49 @@ #include "uncoarsening/refinement/quotient_graph_refinement/complete_boundary.h" #include "uncoarsening/refinement/quotient_graph_refinement/partial_boundary.h" #include "uncoarsening/refinement/quotient_graph_refinement/two_way_refinement.h" - +namespace kahip::modified { class two_way_flow_refinement : public two_way_refinement { - public: - two_way_flow_refinement( ); - virtual ~two_way_flow_refinement(); - - EdgeWeight perform_refinement(PartitionConfig & config, - graph_access & G, - complete_boundary & boundary, - std::vector & lhs_pq_start_nodes, - std::vector & rhs_pq_start_nodes, - boundary_pair * refinement_pair, - NodeWeight & lhs_part_weight, - NodeWeight & rhs_part_weight, - EdgeWeight & cut, - bool & something_changed); - private: - EdgeWeight iterativ_flow_iteration(PartitionConfig & config, - graph_access & G, - complete_boundary & boundary, - std::vector & lhs_pq_start_nodes, - std::vector & rhs_pq_start_nodes, - boundary_pair * refinement_pair, - NodeWeight & lhs_part_weight, - NodeWeight & rhs_part_weight, - EdgeWeight & cut, - bool & something_changed); - - void apply_partition_and_update_boundary(const PartitionConfig & config, - graph_access & G, - boundary_pair * refinement_pair, - PartitionID & lhs, - PartitionID & rhs, - complete_boundary & boundary, - std::vector & lhs_boundary_stripe, - std::vector & rhs_boundary_stripe, - NodeWeight & lhs_stripe_weight, - NodeWeight & rhs_stripe_weight, - std::vector & new_to_old_ids, - std::vector & new_rhs_nodes); +public: + two_way_flow_refinement( ); + virtual ~two_way_flow_refinement(); + + EdgeWeight perform_refinement(PartitionConfig & config, + graph_access & G, + complete_boundary & boundary, + std::vector & lhs_pq_start_nodes, + std::vector & rhs_pq_start_nodes, + boundary_pair * refinement_pair, + NodeWeight & lhs_part_weight, + NodeWeight & rhs_part_weight, + EdgeWeight & cut, + bool & something_changed); +private: + EdgeWeight iterativ_flow_iteration(PartitionConfig & config, + graph_access & G, + complete_boundary & boundary, + std::vector & lhs_pq_start_nodes, + std::vector & rhs_pq_start_nodes, + boundary_pair * refinement_pair, + NodeWeight & lhs_part_weight, + NodeWeight & rhs_part_weight, + EdgeWeight & cut, + bool & something_changed); + + void apply_partition_and_update_boundary(const PartitionConfig & config, + graph_access & G, + boundary_pair * refinement_pair, + PartitionID & lhs, + PartitionID & rhs, + complete_boundary & boundary, + std::vector & lhs_boundary_stripe, + std::vector & rhs_boundary_stripe, + NodeWeight & lhs_stripe_weight, + NodeWeight & rhs_stripe_weight, + std::vector & new_to_old_ids, + std::vector & new_rhs_nodes); }; - +} #endif /* end of include guard: TWO_WAY_FLOW_REFINEMENT_BVTL6G49 */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/partial_boundary.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/partial_boundary.cpp index 5dc7bdc8..fcdbf134 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/partial_boundary.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/partial_boundary.cpp @@ -6,7 +6,7 @@ *****************************************************************************/ #include "partial_boundary.h" - +namespace kahip::modified { PartialBoundary::PartialBoundary() { } @@ -14,4 +14,4 @@ PartialBoundary::PartialBoundary() { PartialBoundary::~PartialBoundary() { } - +} diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/partial_boundary.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/partial_boundary.h index 7739e601..48fe63bf 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/partial_boundary.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/partial_boundary.h @@ -10,7 +10,7 @@ #include #include "definitions.h" - +namespace kahip::modified { struct compare_nodes_contains { bool operator()(const NodeID lhs, const NodeID rhs) const { return (lhs == rhs); @@ -19,32 +19,32 @@ struct compare_nodes_contains { struct is_boundary { - bool contains; - is_boundary() { + bool contains; + is_boundary() { contains = false; - } + } }; struct hash_boundary_nodes { - size_t operator()(const NodeID idx) const { + size_t operator()(const NodeID idx) const { return idx; - } + } }; typedef std::unordered_map is_boundary_node_hashtable; class PartialBoundary { - public: - PartialBoundary( ); - virtual ~PartialBoundary(); +public: + PartialBoundary( ); + virtual ~PartialBoundary(); - bool contains(NodeID node); - void insert(NodeID node); - void deleteNode(NodeID node); - NodeID size(); + bool contains(NodeID node); + void insert(NodeID node); + void deleteNode(NodeID node); + NodeID size(); - is_boundary_node_hashtable internal_boundary; + is_boundary_node_hashtable internal_boundary; }; inline bool PartialBoundary::contains(NodeID node) { @@ -62,7 +62,7 @@ inline void PartialBoundary::deleteNode(NodeID node) { inline NodeID PartialBoundary::size() { return internal_boundary.size(); } - +} //iterator for #define forall_boundary_nodes(boundary, n) { is_boundary_node_hashtable::iterator iter; NodeID n; for(iter = boundary.internal_boundary.begin(); iter != boundary.internal_boundary.end(); iter++ ) { n = iter->first; diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_refinement.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_refinement.cpp index 0c29096f..a28311f1 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_refinement.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_refinement.cpp @@ -16,7 +16,7 @@ #include "quotient_graph_scheduling/simple_quotient_graph_scheduler.h" #include "uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement.h" #include "uncoarsening/refinement/kway_graph_refinement/multitry_kway_fm.h" - +namespace kahip::modified { quotient_graph_refinement::quotient_graph_refinement() { } @@ -31,237 +31,237 @@ void quotient_graph_refinement::setup_start_nodes(graph_access & G, complete_boundary & boundary, boundary_starting_nodes & start_nodes) { - start_nodes.resize(boundary.size(partition, &bp)); - NodeID cur_idx = 0; + start_nodes.resize(boundary.size(partition, &bp)); + NodeID cur_idx = 0; - PartitionID lhs = bp.lhs; - PartitionID rhs = bp.rhs; - PartialBoundary & lhs_b = boundary.getDirectedBoundary(partition, lhs, rhs); + PartitionID lhs = bp.lhs; + PartitionID rhs = bp.rhs; + PartialBoundary & lhs_b = boundary.getDirectedBoundary(partition, lhs, rhs); - forall_boundary_nodes(lhs_b, cur_bnd_node) { - ASSERT_EQ(G.getPartitionIndex(cur_bnd_node), partition); - start_nodes[cur_idx++] = cur_bnd_node; - } endfor + forall_boundary_nodes(lhs_b, cur_bnd_node) { + ASSERT_EQ(G.getPartitionIndex(cur_bnd_node), partition); + start_nodes[cur_idx++] = cur_bnd_node; + } endfor } EdgeWeight quotient_graph_refinement::perform_refinement(PartitionConfig & config, graph_access & G, complete_boundary & boundary) { - ASSERT_TRUE(boundary.assert_bnodes_in_boundaries()); - ASSERT_TRUE(boundary.assert_boundaries_are_bnodes()); - - QuotientGraphEdges qgraph_edges; - boundary.getQuotientGraphEdges(qgraph_edges); - quotient_graph_scheduling* scheduler = NULL; - - int factor = ceil(config.bank_account_factor*qgraph_edges.size()); - switch(config.refinement_scheduling_algorithm) { - case REFINEMENT_SCHEDULING_FAST: - scheduler = new simple_quotient_graph_scheduler(config, qgraph_edges, factor); - break; - case REFINEMENT_SCHEDULING_ACTIVE_BLOCKS: - scheduler = new active_block_quotient_graph_scheduler(config, qgraph_edges, factor); - break; - case REFINEMENT_SCHEDULING_ACTIVE_BLOCKS_REF_KWAY: - scheduler = new active_block_quotient_graph_scheduler(config, qgraph_edges, factor); - break; - } - - EdgeWeight overall_improvement = 0; - unsigned int no_of_pairwise_improvement_steps = 0; - quality_metrics qm; - - do { - no_of_pairwise_improvement_steps++; - // ********** preconditions ******************** - ASSERT_TRUE(boundary.assert_bnodes_in_boundaries()); - ASSERT_TRUE(boundary.assert_boundaries_are_bnodes()); - // *************** end ************************* - - if(scheduler->hasFinished()) break; //fetch the case where we have no qgraph edges - - boundary_pair & bp = scheduler->getNext(); - PartitionID lhs = bp.lhs; - PartitionID rhs = bp.rhs; - - NodeWeight lhs_part_weight = boundary.getBlockWeight(lhs); - NodeWeight rhs_part_weight = boundary.getBlockWeight(rhs); - - EdgeWeight initial_cut_value = boundary.getEdgeCut(&bp); - if( initial_cut_value < 0 ) continue; // quick fix, for bug 02 (very rare cross combine bug / coarsest level) ! - - bool something_changed = false; - -#ifndef NDEBUG - EdgeWeight oldcut = initial_cut_value; + ASSERT_TRUE(boundary.assert_bnodes_in_boundaries()); + ASSERT_TRUE(boundary.assert_boundaries_are_bnodes()); + + QuotientGraphEdges qgraph_edges; + boundary.getQuotientGraphEdges(qgraph_edges); + quotient_graph_scheduling* scheduler = NULL; + + int factor = ceil(config.bank_account_factor*qgraph_edges.size()); + switch(config.refinement_scheduling_algorithm) { + case REFINEMENT_SCHEDULING_FAST: + scheduler = new simple_quotient_graph_scheduler(config, qgraph_edges, factor); + break; + case REFINEMENT_SCHEDULING_ACTIVE_BLOCKS: + scheduler = new active_block_quotient_graph_scheduler(config, qgraph_edges, factor); + break; + case REFINEMENT_SCHEDULING_ACTIVE_BLOCKS_REF_KWAY: + scheduler = new active_block_quotient_graph_scheduler(config, qgraph_edges, factor); + break; + } + + EdgeWeight overall_improvement = 0; + unsigned int no_of_pairwise_improvement_steps = 0; + quality_metrics qm; + + do { + no_of_pairwise_improvement_steps++; + // ********** preconditions ******************** + ASSERT_TRUE(boundary.assert_bnodes_in_boundaries()); + ASSERT_TRUE(boundary.assert_boundaries_are_bnodes()); + // *************** end ************************* + + if(scheduler->hasFinished()) break; //fetch the case where we have no qgraph edges + + boundary_pair & bp = scheduler->getNext(); + PartitionID lhs = bp.lhs; + PartitionID rhs = bp.rhs; + + NodeWeight lhs_part_weight = boundary.getBlockWeight(lhs); + NodeWeight rhs_part_weight = boundary.getBlockWeight(rhs); + + EdgeWeight initial_cut_value = boundary.getEdgeCut(&bp); + if( initial_cut_value < 0 ) continue; // quick fix, for bug 02 (very rare cross combine bug / coarsest level) ! + + bool something_changed = false; + +#ifndef NDEBUG + EdgeWeight oldcut = initial_cut_value; #endif - PartitionConfig cfg = config; - EdgeWeight improvement = perform_a_two_way_refinement(cfg, G, boundary, bp, - lhs, rhs, - lhs_part_weight, rhs_part_weight, - initial_cut_value, something_changed); - - overall_improvement += improvement; - - EdgeWeight multitry_improvement = 0; - if(config.refinement_scheduling_algorithm == REFINEMENT_SCHEDULING_ACTIVE_BLOCKS_REF_KWAY ) { - multitry_kway_fm kway_ref; - std::unordered_map touched_blocks; - - multitry_improvement = kway_ref.perform_refinement_around_parts(cfg, G, - boundary, true, - config.local_multitry_fm_alpha, lhs, rhs, - touched_blocks); - - if(multitry_improvement > 0) { - ((active_block_quotient_graph_scheduler*)scheduler)->activate_blocks(touched_blocks); - } - - } - - qgraph_edge_statistics stat(improvement, &bp, something_changed); - scheduler->pushStatistics(stat); - - //**************** assertions / postconditions ************************** - ASSERT_TRUE( oldcut - improvement == qm.edge_cut(G, lhs, rhs) - || config.refinement_scheduling_algorithm == REFINEMENT_SCHEDULING_ACTIVE_BLOCKS_REF_KWAY); - ASSERT_TRUE(boundary.assert_bnodes_in_boundaries()); - ASSERT_TRUE(boundary.assert_boundaries_are_bnodes()); - ASSERT_TRUE(boundary.getBlockNoNodes(lhs)>0); - ASSERT_TRUE(boundary.getBlockNoNodes(rhs)>0); - //*************************** end **************************************** - } while(!scheduler->hasFinished()); - - delete scheduler; - return overall_improvement; + PartitionConfig cfg = config; + EdgeWeight improvement = perform_a_two_way_refinement(cfg, G, boundary, bp, + lhs, rhs, + lhs_part_weight, rhs_part_weight, + initial_cut_value, something_changed); + + overall_improvement += improvement; + + EdgeWeight multitry_improvement = 0; + if(config.refinement_scheduling_algorithm == REFINEMENT_SCHEDULING_ACTIVE_BLOCKS_REF_KWAY ) { + multitry_kway_fm kway_ref; + std::unordered_map touched_blocks; + + multitry_improvement = kway_ref.perform_refinement_around_parts(cfg, G, + boundary, true, + config.local_multitry_fm_alpha, lhs, rhs, + touched_blocks); + + if(multitry_improvement > 0) { + ((active_block_quotient_graph_scheduler*)scheduler)->activate_blocks(touched_blocks); + } + + } + + qgraph_edge_statistics stat(improvement, &bp, something_changed); + scheduler->pushStatistics(stat); + + //**************** assertions / postconditions ************************** + ASSERT_TRUE( oldcut - improvement == qm.edge_cut(G, lhs, rhs) + || config.refinement_scheduling_algorithm == REFINEMENT_SCHEDULING_ACTIVE_BLOCKS_REF_KWAY); + ASSERT_TRUE(boundary.assert_bnodes_in_boundaries()); + ASSERT_TRUE(boundary.assert_boundaries_are_bnodes()); + ASSERT_TRUE(boundary.getBlockNoNodes(lhs)>0); + ASSERT_TRUE(boundary.getBlockNoNodes(rhs)>0); + //*************************** end **************************************** + } while(!scheduler->hasFinished()); + + delete scheduler; + return overall_improvement; } -EdgeWeight quotient_graph_refinement::perform_a_two_way_refinement(PartitionConfig & config, +EdgeWeight quotient_graph_refinement::perform_a_two_way_refinement(PartitionConfig & config, graph_access & G, complete_boundary & boundary, boundary_pair & bp, - PartitionID & lhs, + PartitionID & lhs, PartitionID & rhs, NodeWeight & lhs_part_weight, NodeWeight & rhs_part_weight, EdgeWeight & initial_cut_value, bool & something_changed) { - two_way_fm pair_wise_refinement; - two_way_flow_refinement pair_wise_flow; - - std::vector lhs_bnd_nodes; - setup_start_nodes(G, lhs, bp, boundary, lhs_bnd_nodes); - - std::vector rhs_bnd_nodes; + two_way_fm pair_wise_refinement; + two_way_flow_refinement pair_wise_flow; + + std::vector lhs_bnd_nodes; + setup_start_nodes(G, lhs, bp, boundary, lhs_bnd_nodes); + + std::vector rhs_bnd_nodes; + setup_start_nodes(G, rhs, bp, boundary, rhs_bnd_nodes); + + something_changed = false; + EdgeWeight improvement = 0; + + quality_metrics qm; + if(config.refinement_type == REFINEMENT_TYPE_FM_FLOW || config.refinement_type == REFINEMENT_TYPE_FM) { + improvement = pair_wise_refinement.perform_refinement(config, + G, + boundary, + lhs_bnd_nodes, + rhs_bnd_nodes, + &bp, + lhs_part_weight, + rhs_part_weight, + initial_cut_value, + something_changed); + ASSERT_TRUE(improvement >= 0 || config.rebalance); + } + + if(config.refinement_type == REFINEMENT_TYPE_FM_FLOW || config.refinement_type == REFINEMENT_TYPE_FLOW){ + lhs_bnd_nodes.clear(); + setup_start_nodes(G, lhs, bp, boundary, lhs_bnd_nodes); + + rhs_bnd_nodes.clear(); + setup_start_nodes(G, rhs, bp, boundary, rhs_bnd_nodes); + + EdgeWeight _improvement = pair_wise_flow.perform_refinement(config, + G, + boundary, + lhs_bnd_nodes, + rhs_bnd_nodes, + &bp, + lhs_part_weight, + rhs_part_weight, + initial_cut_value, + something_changed); + + ASSERT_TRUE(_improvement >= 0 || config.rebalance); + improvement += _improvement; + } + + bool only_one_block_is_overloaded = boundary.getBlockWeight(lhs) > config.upper_bound_partition; + only_one_block_is_overloaded = only_one_block_is_overloaded + || boundary.getBlockWeight(rhs) > config.upper_bound_partition; + only_one_block_is_overloaded = only_one_block_is_overloaded && + (boundary.getBlockWeight(lhs) <= config.upper_bound_partition || + boundary.getBlockWeight(rhs) <= config.upper_bound_partition); + + if(only_one_block_is_overloaded) { + + PartitionConfig cfg = config; + cfg.softrebalance = true; + cfg.rebalance = false; + + lhs_bnd_nodes.clear(); + setup_start_nodes(G, lhs, bp, boundary, lhs_bnd_nodes); + + rhs_bnd_nodes.clear(); + setup_start_nodes(G, rhs, bp, boundary, rhs_bnd_nodes); + + improvement += pair_wise_refinement.perform_refinement(cfg, + G, + boundary, + lhs_bnd_nodes, + rhs_bnd_nodes, + &bp, + lhs_part_weight, + rhs_part_weight, + initial_cut_value, + something_changed); + + ASSERT_TRUE(improvement >= 0 || config.rebalance); + + if(!config.disable_hard_rebalance && !config.kaffpa_perfectly_balanced_refinement && !config.initial_bipartitioning) { + only_one_block_is_overloaded = boundary.getBlockWeight(lhs) > config.upper_bound_partition; + only_one_block_is_overloaded = only_one_block_is_overloaded + || boundary.getBlockWeight(rhs) > config.upper_bound_partition; + only_one_block_is_overloaded = only_one_block_is_overloaded && + (boundary.getBlockWeight(lhs) <= config.upper_bound_partition || + boundary.getBlockWeight(rhs) <= config.upper_bound_partition); + + if(only_one_block_is_overloaded) { + cfg.softrebalance = true; + cfg.rebalance = true; + + lhs_bnd_nodes.clear(); + setup_start_nodes(G, lhs, bp, boundary, lhs_bnd_nodes); + + rhs_bnd_nodes.clear(); setup_start_nodes(G, rhs, bp, boundary, rhs_bnd_nodes); - something_changed = false; - EdgeWeight improvement = 0; - - quality_metrics qm; - if(config.refinement_type == REFINEMENT_TYPE_FM_FLOW || config.refinement_type == REFINEMENT_TYPE_FM) { - improvement = pair_wise_refinement.perform_refinement(config, - G, - boundary, - lhs_bnd_nodes, - rhs_bnd_nodes, - &bp, - lhs_part_weight, - rhs_part_weight, - initial_cut_value, - something_changed); - ASSERT_TRUE(improvement >= 0 || config.rebalance); - } - - if(config.refinement_type == REFINEMENT_TYPE_FM_FLOW || config.refinement_type == REFINEMENT_TYPE_FLOW){ - lhs_bnd_nodes.clear(); - setup_start_nodes(G, lhs, bp, boundary, lhs_bnd_nodes); - - rhs_bnd_nodes.clear(); - setup_start_nodes(G, rhs, bp, boundary, rhs_bnd_nodes); - - EdgeWeight _improvement = pair_wise_flow.perform_refinement(config, - G, - boundary, - lhs_bnd_nodes, - rhs_bnd_nodes, - &bp, - lhs_part_weight, - rhs_part_weight, - initial_cut_value, - something_changed); - - ASSERT_TRUE(_improvement >= 0 || config.rebalance); - improvement += _improvement; - } - - bool only_one_block_is_overloaded = boundary.getBlockWeight(lhs) > config.upper_bound_partition; - only_one_block_is_overloaded = only_one_block_is_overloaded - || boundary.getBlockWeight(rhs) > config.upper_bound_partition; - only_one_block_is_overloaded = only_one_block_is_overloaded && - (boundary.getBlockWeight(lhs) <= config.upper_bound_partition || - boundary.getBlockWeight(rhs) <= config.upper_bound_partition); - - if(only_one_block_is_overloaded) { - - PartitionConfig cfg = config; - cfg.softrebalance = true; - cfg.rebalance = false; - - lhs_bnd_nodes.clear(); - setup_start_nodes(G, lhs, bp, boundary, lhs_bnd_nodes); - - rhs_bnd_nodes.clear(); - setup_start_nodes(G, rhs, bp, boundary, rhs_bnd_nodes); - - improvement += pair_wise_refinement.perform_refinement(cfg, - G, - boundary, - lhs_bnd_nodes, - rhs_bnd_nodes, - &bp, - lhs_part_weight, - rhs_part_weight, - initial_cut_value, - something_changed); - - ASSERT_TRUE(improvement >= 0 || config.rebalance); - - if(!config.disable_hard_rebalance && !config.kaffpa_perfectly_balanced_refinement && !config.initial_bipartitioning) { - only_one_block_is_overloaded = boundary.getBlockWeight(lhs) > config.upper_bound_partition; - only_one_block_is_overloaded = only_one_block_is_overloaded - || boundary.getBlockWeight(rhs) > config.upper_bound_partition; - only_one_block_is_overloaded = only_one_block_is_overloaded && - (boundary.getBlockWeight(lhs) <= config.upper_bound_partition || - boundary.getBlockWeight(rhs) <= config.upper_bound_partition); - - if(only_one_block_is_overloaded) { - cfg.softrebalance = true; - cfg.rebalance = true; - - lhs_bnd_nodes.clear(); - setup_start_nodes(G, lhs, bp, boundary, lhs_bnd_nodes); - - rhs_bnd_nodes.clear(); - setup_start_nodes(G, rhs, bp, boundary, rhs_bnd_nodes); - - improvement += pair_wise_refinement.perform_refinement(cfg, - G, - boundary, - lhs_bnd_nodes, - rhs_bnd_nodes, - &bp, - lhs_part_weight, - rhs_part_weight, - initial_cut_value, - something_changed); - - } - } - } - - return improvement; + improvement += pair_wise_refinement.perform_refinement(cfg, + G, + boundary, + lhs_bnd_nodes, + rhs_bnd_nodes, + &bp, + lhs_part_weight, + rhs_part_weight, + initial_cut_value, + something_changed); + + } + } + } + + return improvement; +} } - diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_refinement.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_refinement.h index 3bf6a2af..7a753c77 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_refinement.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_refinement.h @@ -10,33 +10,33 @@ #include "definitions.h" #include "uncoarsening/refinement/refinement.h" - +namespace kahip::modified { class quotient_graph_refinement : public refinement { - public: - quotient_graph_refinement( ); - virtual ~quotient_graph_refinement(); - - EdgeWeight perform_refinement(PartitionConfig & config, graph_access & G, complete_boundary & boundary); - - void setup_start_nodes(graph_access & G, - PartitionID partition, - boundary_pair & bp, - complete_boundary & boundary, - boundary_starting_nodes & start_nodes); - - private: - EdgeWeight perform_a_two_way_refinement(PartitionConfig & config, - graph_access & G, - complete_boundary & boundary, - boundary_pair & bp, - PartitionID & lhs, - PartitionID & rhs, - NodeWeight & lhs_part_weight, - NodeWeight & rhs_part_weight, - EdgeWeight & cut, - bool & something_changed); +public: + quotient_graph_refinement( ); + virtual ~quotient_graph_refinement(); + + EdgeWeight perform_refinement(PartitionConfig & config, graph_access & G, complete_boundary & boundary); + + void setup_start_nodes(graph_access & G, + PartitionID partition, + boundary_pair & bp, + complete_boundary & boundary, + boundary_starting_nodes & start_nodes); + +private: + EdgeWeight perform_a_two_way_refinement(PartitionConfig & config, + graph_access & G, + complete_boundary & boundary, + boundary_pair & bp, + PartitionID & lhs, + PartitionID & rhs, + NodeWeight & lhs_part_weight, + NodeWeight & rhs_part_weight, + EdgeWeight & cut, + bool & something_changed); }; - +} #endif /* end of include guard: QUOTIENT_GRAPH_REFINEMENT_A0Y1Y6LL */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/active_block_quotient_graph_scheduler.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/active_block_quotient_graph_scheduler.cpp index 646080f9..16755ef2 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/active_block_quotient_graph_scheduler.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/active_block_quotient_graph_scheduler.cpp @@ -6,22 +6,22 @@ *****************************************************************************/ #include "active_block_quotient_graph_scheduler.h" - -active_block_quotient_graph_scheduler::active_block_quotient_graph_scheduler( const PartitionConfig & config, +namespace kahip::modified { +active_block_quotient_graph_scheduler::active_block_quotient_graph_scheduler( const PartitionConfig & config, QuotientGraphEdges & qgraph_edges, unsigned int bank_account) : m_quotient_graph_edges(qgraph_edges) { - m_is_block_active.resize(config.k); - for( unsigned int i = 0; i < m_is_block_active.size(); i++) { - m_is_block_active[i] = true; - } - - m_no_of_active_blocks = config.k; - init(); + m_is_block_active.resize(config.k); + for( unsigned int i = 0; i < m_is_block_active.size(); i++) { + m_is_block_active[i] = true; + } + + m_no_of_active_blocks = config.k; + init(); } active_block_quotient_graph_scheduler::~active_block_quotient_graph_scheduler() { } - +} diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/active_block_quotient_graph_scheduler.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/active_block_quotient_graph_scheduler.h index f185151b..c596bac4 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/active_block_quotient_graph_scheduler.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/active_block_quotient_graph_scheduler.h @@ -13,27 +13,27 @@ #include "partition_config.h" #include "quotient_graph_scheduling.h" #include "random_functions.h" - +namespace kahip::modified { class active_block_quotient_graph_scheduler : public quotient_graph_scheduling { - public: - active_block_quotient_graph_scheduler( const PartitionConfig & config, - QuotientGraphEdges & qgraph_edges, - unsigned int bank_account); +public: + active_block_quotient_graph_scheduler( const PartitionConfig & config, + QuotientGraphEdges & qgraph_edges, + unsigned int bank_account); - virtual ~active_block_quotient_graph_scheduler(); + virtual ~active_block_quotient_graph_scheduler(); - virtual bool hasFinished(); - virtual boundary_pair & getNext(); - virtual void pushStatistics(qgraph_edge_statistics & statistic); - virtual void init(); + virtual bool hasFinished(); + virtual boundary_pair & getNext(); + virtual void pushStatistics(qgraph_edge_statistics & statistic); + virtual void init(); - void activate_blocks(std::unordered_map & blocks); + void activate_blocks(std::unordered_map & blocks); - private: - QuotientGraphEdges & m_quotient_graph_edges; - QuotientGraphEdges m_active_quotient_graph_edges; - PartitionID m_no_of_active_blocks; - std::vector m_is_block_active; +private: + QuotientGraphEdges & m_quotient_graph_edges; + QuotientGraphEdges m_active_quotient_graph_edges; + PartitionID m_no_of_active_blocks; + std::vector m_is_block_active; }; inline void active_block_quotient_graph_scheduler::init() { @@ -41,8 +41,8 @@ inline void active_block_quotient_graph_scheduler::init() { m_active_quotient_graph_edges.clear(); for( unsigned int i = 0; i < m_quotient_graph_edges.size(); i++) { - PartitionID lhs = m_quotient_graph_edges[i].lhs; - PartitionID rhs = m_quotient_graph_edges[i].rhs; + PartitionID lhs = m_quotient_graph_edges[i].lhs; + PartitionID rhs = m_quotient_graph_edges[i].rhs; if(m_is_block_active[lhs]) m_no_of_active_blocks++; if(m_is_block_active[rhs]) m_no_of_active_blocks++; @@ -64,14 +64,14 @@ inline bool active_block_quotient_graph_scheduler::hasFinished( ) { init(); } - return m_no_of_active_blocks == 0; + return m_no_of_active_blocks == 0; } inline boundary_pair & active_block_quotient_graph_scheduler::getNext( ) { boundary_pair & ret_value = m_active_quotient_graph_edges.back(); m_active_quotient_graph_edges.pop_back(); - return ret_value; + return ret_value; } inline void active_block_quotient_graph_scheduler::pushStatistics(qgraph_edge_statistics & statistic) { @@ -84,8 +84,8 @@ inline void active_block_quotient_graph_scheduler::pushStatistics(qgraph_edge_st inline void active_block_quotient_graph_scheduler::activate_blocks(std::unordered_map & blocks) { std::unordered_map::iterator it; for(it = blocks.begin(); it != blocks.end(); ++it) { - m_is_block_active[it->first] = true; + m_is_block_active[it->first] = true; } } - +} #endif /* end of include guard: ACTIVE_BLOCK_QUOTIENT_GRAPH_SCHEDULER_2QATIGSY */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/quotient_graph_scheduling.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/quotient_graph_scheduling.cpp index ee1e251a..77358e16 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/quotient_graph_scheduling.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/quotient_graph_scheduling.cpp @@ -6,11 +6,11 @@ *****************************************************************************/ #include "quotient_graph_scheduling.h" - +namespace kahip::modified { quotient_graph_scheduling::quotient_graph_scheduling() { } quotient_graph_scheduling::~quotient_graph_scheduling() { } - +} diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/quotient_graph_scheduling.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/quotient_graph_scheduling.h index 69ac3ed9..04f48e7b 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/quotient_graph_scheduling.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/quotient_graph_scheduling.h @@ -10,29 +10,29 @@ #include "definitions.h" #include "uncoarsening/refinement/quotient_graph_refinement/complete_boundary.h" - +namespace kahip::modified { struct qgraph_edge_statistics { - EdgeWeight improvement; - bool something_changed; - boundary_pair* pair; + EdgeWeight improvement; + bool something_changed; + boundary_pair* pair; - qgraph_edge_statistics(EdgeWeight _improvement, - boundary_pair* bp, - bool change) : improvement(_improvement), something_changed(change), pair(bp){ - } + qgraph_edge_statistics(EdgeWeight _improvement, + boundary_pair* bp, + bool change) : improvement(_improvement), something_changed(change), pair(bp){ + } }; class quotient_graph_scheduling { - public: - quotient_graph_scheduling(); - virtual ~quotient_graph_scheduling(); +public: + quotient_graph_scheduling(); + virtual ~quotient_graph_scheduling(); - virtual bool hasFinished() = 0; - virtual boundary_pair & getNext() = 0; - virtual void pushStatistics(qgraph_edge_statistics & statistic) = 0; + virtual bool hasFinished() = 0; + virtual boundary_pair & getNext() = 0; + virtual void pushStatistics(qgraph_edge_statistics & statistic) = 0; }; - +} #endif /* end of include guard: QUOTIENT_GRAPH_SCHEDULING_NEFT9H3J */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/simple_quotient_graph_scheduler.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/simple_quotient_graph_scheduler.cpp index 74270d6f..ee79d734 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/simple_quotient_graph_scheduler.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/simple_quotient_graph_scheduler.cpp @@ -7,22 +7,22 @@ #include "random_functions.h" #include "simple_quotient_graph_scheduler.h" - -simple_quotient_graph_scheduler::simple_quotient_graph_scheduler(PartitionConfig & config, +namespace kahip::modified { +simple_quotient_graph_scheduler::simple_quotient_graph_scheduler(PartitionConfig & config, QuotientGraphEdges & qgraph_edges, unsigned int account) { - unsigned added_edges = 0; - for( unsigned i = 0; i < (unsigned)ceil(config.bank_account_factor) && added_edges <= account; i++) { - random_functions::permutate_vector_good_small(qgraph_edges); - for( unsigned i = 0; i < qgraph_edges.size() && added_edges <= account; i++) { - m_quotient_graph_edges_pool.push_back(qgraph_edges[i]); - added_edges++; - } - } + unsigned added_edges = 0; + for( unsigned i = 0; i < (unsigned)ceil(config.bank_account_factor) && added_edges <= account; i++) { + random_functions::permutate_vector_good_small(qgraph_edges); + for( unsigned i = 0; i < qgraph_edges.size() && added_edges <= account; i++) { + m_quotient_graph_edges_pool.push_back(qgraph_edges[i]); + added_edges++; + } + } } simple_quotient_graph_scheduler::~simple_quotient_graph_scheduler() { } - +} diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/simple_quotient_graph_scheduler.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/simple_quotient_graph_scheduler.h index 0fed53b4..16116486 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/simple_quotient_graph_scheduler.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/simple_quotient_graph_scheduler.h @@ -10,8 +10,8 @@ #include "partition_config.h" #include "quotient_graph_scheduling.h" - -class simple_quotient_graph_scheduler : public quotient_graph_scheduling { +namespace kahip::modified { +class simple_quotient_graph_scheduler : public quotient_graph_scheduling { public: simple_quotient_graph_scheduler(PartitionConfig & config, QuotientGraphEdges & qgraph_edges, @@ -36,5 +36,5 @@ inline boundary_pair & simple_quotient_graph_scheduler::getNext( ) { m_quotient_graph_edges_pool.pop_back(); return ret_value; } - +} #endif /* end of include guard: SIMPLE_QUOTIENT_GRAPH_SCHEDULER_YG9BEBH0 */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/two_way_refinement.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/two_way_refinement.h index 31bee55d..33744cba 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/two_way_refinement.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/two_way_refinement.h @@ -9,24 +9,24 @@ #define TWO_WAY_REFINEMENT_INTERFACE_1ZWCSI0J #include "definitions.h" - +namespace kahip::modified { class two_way_refinement{ - public: - two_way_refinement( ) {}; - virtual ~two_way_refinement() {}; +public: + two_way_refinement( ) {}; + virtual ~two_way_refinement() {}; - virtual EdgeWeight perform_refinement(PartitionConfig & config, - graph_access & G, - complete_boundary & boundary, - std::vector & lhs_pq_start_nodes, - std::vector & rhs_pq_start_nodes, - boundary_pair * refinement_pair, - NodeWeight & lhs_part_weight, - NodeWeight & rhs_part_weight, - EdgeWeight & cut, - bool & something_changed) = 0; + virtual EdgeWeight perform_refinement(PartitionConfig & config, + graph_access & G, + complete_boundary & boundary, + std::vector & lhs_pq_start_nodes, + std::vector & rhs_pq_start_nodes, + boundary_pair * refinement_pair, + NodeWeight & lhs_part_weight, + NodeWeight & rhs_part_weight, + EdgeWeight & cut, + bool & something_changed) = 0; }; - +} #endif /* end of include guard: TWO_WAY_REFINEMENT_INTERFACE_1ZWCSI0J */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/refinement.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/refinement.cpp index 83de9564..b56b23d7 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/refinement.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/refinement.cpp @@ -6,7 +6,7 @@ *****************************************************************************/ #include "refinement.h" - +namespace kahip::modified { refinement::refinement() { } @@ -14,6 +14,6 @@ refinement::refinement() { refinement::~refinement() { } - +} diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/refinement.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/refinement.h index 778e3beb..da13fc96 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/refinement.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/refinement.h @@ -12,15 +12,16 @@ #include "partition_config.h" #include "quotient_graph_refinement/complete_boundary.h" +namespace kahip::modified { class refinement { public: - refinement( ); - virtual ~refinement(); - - virtual EdgeWeight perform_refinement(PartitionConfig & config, - graph_access & G, - complete_boundary & boundary) = 0; -}; + refinement( ); + virtual ~refinement(); + virtual EdgeWeight perform_refinement(PartitionConfig & config, + graph_access & G, + complete_boundary & boundary) = 0; +}; +} #endif /* end of include guard: REFINEMENT_UJN9IBHM */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/tabu_search/tabu_bucket_queue.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/tabu_search/tabu_bucket_queue.h index 0e3e746e..3a832227 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/tabu_search/tabu_bucket_queue.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/tabu_search/tabu_bucket_queue.h @@ -9,47 +9,48 @@ #define TABU_BUCKET_PQ_EM8YJPA9 #include - //this PQ is specalized for Tabu Search, it only contains non-tabu moves //there is a second PQ that contains tabu moves #include "data_structure/matrix/normal_matrix.h" #include "data_structure/priority_queues/priority_queue_interface.h" #include "random_functions.h" +namespace kahip::modified { + class tabu_bucket_queue { - public: - tabu_bucket_queue( PartitionConfig & config, const EdgeWeight & gain_span, NodeID number_of_nodes ); - - virtual ~tabu_bucket_queue() { delete m_queue_index; delete m_gains;}; - - NodeID size(); - void insert(NodeID id, PartitionID block, Gain gain); - bool empty(); - - Gain maxValue(); - std::pair maxElement(); - std::pair deleteMax(); - - void decreaseKey(NodeID node, PartitionID block, Gain newGain); - void increaseKey(NodeID node, PartitionID block, Gain newGain); - - void changeKey(NodeID element, PartitionID block, Gain newKey); - Gain getKey(NodeID element, PartitionID block); - void deleteNode(NodeID node, PartitionID block); - - bool contains(NodeID node, PartitionID block); - private: - normal_matrix* m_queue_index; - normal_matrix* m_gains; - NodeID m_elements; - EdgeWeight m_gain_span; - unsigned m_max_idx; //points to the non-empty bucket with the largest gain - - std::vector< std::vector< std::pair > > m_buckets; +public: + tabu_bucket_queue( PartitionConfig & config, const EdgeWeight & gain_span, NodeID number_of_nodes ); + + virtual ~tabu_bucket_queue() { delete m_queue_index; delete m_gains;}; + + NodeID size(); + void insert(NodeID id, PartitionID block, Gain gain); + bool empty(); + + Gain maxValue(); + std::pair maxElement(); + std::pair deleteMax(); + + void decreaseKey(NodeID node, PartitionID block, Gain newGain); + void increaseKey(NodeID node, PartitionID block, Gain newGain); + + void changeKey(NodeID element, PartitionID block, Gain newKey); + Gain getKey(NodeID element, PartitionID block); + void deleteNode(NodeID node, PartitionID block); + + bool contains(NodeID node, PartitionID block); +private: + normal_matrix* m_queue_index; + normal_matrix* m_gains; + NodeID m_elements; + EdgeWeight m_gain_span; + unsigned m_max_idx; //points to the non-empty bucket with the largest gain + + std::vector< std::vector< std::pair > > m_buckets; }; -inline tabu_bucket_queue::tabu_bucket_queue( PartitionConfig & config, - const EdgeWeight & gain_span_input, +inline tabu_bucket_queue::tabu_bucket_queue( PartitionConfig & config, + const EdgeWeight & gain_span_input, NodeID number_of_nodes ) { m_elements = 0; m_gain_span = gain_span_input; @@ -60,62 +61,62 @@ inline tabu_bucket_queue::tabu_bucket_queue( PartitionConfig & config, } inline NodeID tabu_bucket_queue::size() { - return m_elements; + return m_elements; } inline void tabu_bucket_queue::insert(NodeID node, PartitionID block, Gain gain) { unsigned address = gain + m_gain_span; if(address > m_max_idx) { - m_max_idx = address; + m_max_idx = address; } - + std::pair< NodeID, PartitionID > p; p.first = node; p.second = block; - m_buckets[address].push_back( p ); + m_buckets[address].push_back( p ); m_queue_index->set_xy(node, block, m_buckets[address].size() - 1); //store position m_gains->set_xy(node, block, gain); - + m_elements++; } inline bool tabu_bucket_queue::empty( ) { - return m_elements == 0; + return m_elements == 0; } inline Gain tabu_bucket_queue::maxValue( ) { - return m_max_idx - m_gain_span; + return m_max_idx - m_gain_span; } inline std::pair tabu_bucket_queue::maxElement( ) { - return m_buckets[m_max_idx].back(); + return m_buckets[m_max_idx].back(); } inline std::pair tabu_bucket_queue::deleteMax() { - unsigned rnd_idx = random_functions::nextInt(0, m_buckets[m_max_idx].size()-1); - swap(m_buckets[m_max_idx][rnd_idx], m_buckets[m_max_idx].back()); - m_queue_index->set_xy(m_buckets[m_max_idx][rnd_idx].first, m_buckets[m_max_idx][rnd_idx].second, rnd_idx); - - std::pair< NodeID, PartitionID > p; - p = m_buckets[m_max_idx].back(); - m_buckets[m_max_idx].pop_back(); - - m_queue_index->set_xy(p.first, p.second, NOTINQUEUE); //erase(node, block); - m_gains->set_xy(p.first, p.second, NOTINQUEUE); - - if( m_buckets[m_max_idx].size() == 0 ) { - //update max_idx - while( m_max_idx != 0 ) { - m_max_idx--; - if(m_buckets[m_max_idx].size() > 0) { - break; - } - } - } - - m_elements--; - return p; + unsigned rnd_idx = random_functions::nextInt(0, m_buckets[m_max_idx].size()-1); + swap(m_buckets[m_max_idx][rnd_idx], m_buckets[m_max_idx].back()); + m_queue_index->set_xy(m_buckets[m_max_idx][rnd_idx].first, m_buckets[m_max_idx][rnd_idx].second, rnd_idx); + + std::pair< NodeID, PartitionID > p; + p = m_buckets[m_max_idx].back(); + m_buckets[m_max_idx].pop_back(); + + m_queue_index->set_xy(p.first, p.second, NOTINQUEUE); //erase(node, block); + m_gains->set_xy(p.first, p.second, NOTINQUEUE); + + if( m_buckets[m_max_idx].size() == 0 ) { + //update max_idx + while( m_max_idx != 0 ) { + m_max_idx--; + if(m_buckets[m_max_idx].size() > 0) { + break; + } + } + } + + m_elements--; + return p; } inline void tabu_bucket_queue::decreaseKey(NodeID node, PartitionID block, Gain new_gain) { @@ -170,6 +171,6 @@ inline void tabu_bucket_queue::deleteNode(NodeID node, PartitionID block) { inline bool tabu_bucket_queue::contains(NodeID node, PartitionID block) { return m_queue_index->get_xy(node, block) != NOTINQUEUE; } - +} #endif /* end of include guard: BUCKET_PQ_EM8YJPA9 */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/tabu_search/tabu_moves_queue.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/tabu_search/tabu_moves_queue.h index 1767ed53..9a0ad45c 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/tabu_search/tabu_moves_queue.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/tabu_search/tabu_moves_queue.h @@ -12,7 +12,7 @@ #include #include "data_structure/matrix/normal_matrix.h" - +namespace kahip::modified { struct TabuTimePair { int time; NodeID node; @@ -28,20 +28,20 @@ struct comparePair{ typedef std::priority_queue< TabuTimePair, std::vector< TabuTimePair >, comparePair > PQ; class tabu_moves_queue { - public: - tabu_moves_queue( ); - virtual ~tabu_moves_queue() { }; +public: + tabu_moves_queue( ); + virtual ~tabu_moves_queue() { }; - NodeID size(); - bool empty(); + NodeID size(); + bool empty(); - void insert(NodeID node, PartitionID block, int time); - int minValue(); - std::pair deleteMin(); + void insert(NodeID node, PartitionID block, int time); + int minValue(); + std::pair deleteMin(); - bool contains(NodeID node, PartitionID block); - private: - PQ m_priority_queue; + bool contains(NodeID node, PartitionID block); +private: + PQ m_priority_queue; }; inline tabu_moves_queue::tabu_moves_queue() { @@ -74,5 +74,5 @@ inline std::pair tabu_moves_queue::deleteMin() { m_priority_queue.pop(); return p; } - +} #endif /* end of include guard: BUCKET_PQ_EM8YJPA9 */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/tabu_search/tabu_search.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/tabu_search/tabu_search.cpp index bf32efd3..afb31cca 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/tabu_search/tabu_search.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/tabu_search/tabu_search.cpp @@ -13,7 +13,7 @@ #include "uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement.h" #include "uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_commons.h" #include "uncoarsening/refinement/kway_graph_refinement/kway_stop_rule.h" - +namespace kahip::modified { tabu_search::tabu_search() { } @@ -23,227 +23,228 @@ tabu_search::~tabu_search() { } EdgeWeight tabu_search::perform_refinement(PartitionConfig & config, graph_access & G, complete_boundary & boundary) { - quality_metrics qm; - EdgeWeight input_cut = qm.edge_cut(G); - EdgeWeight cur_cut = input_cut; - EdgeWeight best_cut = input_cut; - std::vector< PartitionID > bestmap(G.number_of_nodes(), 0); - forall_nodes(G, node) { - bestmap[node] = G.getPartitionIndex(node); + quality_metrics qm; + EdgeWeight input_cut = qm.edge_cut(G); + EdgeWeight cur_cut = input_cut; + EdgeWeight best_cut = input_cut; + std::vector< PartitionID > bestmap(G.number_of_nodes(), 0); + forall_nodes(G, node) { + bestmap[node] = G.getPartitionIndex(node); + } endfor + + + EdgeWeight max_degree = G.getMaxDegree(); + tabu_bucket_queue* queue = new tabu_bucket_queue(config, max_degree, G.number_of_nodes()); + tabu_moves_queue* tabu_moves = new tabu_moves_queue(); + + matrix* T = new normal_matrix(G.number_of_nodes(), config.k); + matrix* gamma = new normal_matrix(G.number_of_nodes(), config.k); + + forall_nodes(G, node) { + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + PartitionID target_block = G.getPartitionIndex(target); + gamma->set_xy( node, target_block, gamma->get_xy(node, target_block) + 1); + } endfor +} endfor + +forall_nodes(G, node) { + bool is_bnd = false; + PartitionID pIdx = G.getPartitionIndex(node); + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + if( pIdx != G.getPartitionIndex(target)) { + is_bnd = true; + break; + } + } endfor + + if(is_bnd) { + for( unsigned block = 0; block < config.k; block++) { + if( gamma->get_xy(node, block) > 0 && G.getPartitionIndex(node) != block) { + queue->insert(node, block, gamma->get_xy(node, block) - gamma->get_xy(node, G.getPartitionIndex(node))); + } else { + tabu_moves->insert(node, block, 0); + } + } + } + } endfor + + unsigned no_impro_iterations = 0; + config.maxT = random_functions::nextInt(50, 3000); + unsigned iteration_limit = std::min((int)(2*G.number_of_nodes()), 40000); + + std::vector< std::pair > undo_buffer; + undo_buffer.reserve(G.number_of_edges()); + + std::vector cur_state(G.number_of_nodes()); + int best_idx = -1; int round_counter = -1; unsigned iteration = 0; + + for( iteration = 0, round_counter = 0; iteration < config.maxIter; iteration++) { + if(!queue->empty()) { + Gain gain = queue->maxValue(); + std::pair< NodeID, PartitionID > p = queue->deleteMax(); + NodeID node = p.first; + NodeID block = p.second; + NodeID from = G.getPartitionIndex(node); + + if( boundary.getBlockWeight(block) + 1 < config.upper_bound_partition && from != block) { + boundary.setBlockWeight(from, boundary.getBlockWeight(from) - 1); + boundary.setBlockWeight(block, boundary.getBlockWeight(block) + 1); + + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + gamma->set_xy( target, from, gamma->get_xy(target, from) - 1); + gamma->set_xy( target, block, gamma->get_xy(target, block) + 1); + } endfor + + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + PartitionID target_block = G.getPartitionIndex(target); + + for( unsigned i = 0; i < config.k; i++) { + if(queue->contains( target, i )) { + if( gamma->get_xy(target, i) == 0) { + queue->deleteNode(target, i); + } else { + queue->changeKey(target, i, gamma->get_xy(target, i) - gamma->get_xy(target, target_block)); + } + } else { + if( gamma->get_xy(target, i) > 0 && T->get_xy(target, i) < (int)iteration) { + queue->insert(target, i, gamma->get_xy(target, i) - gamma->get_xy(target, target_block)); + } + } + } } endfor - - EdgeWeight max_degree = G.getMaxDegree(); - tabu_bucket_queue* queue = new tabu_bucket_queue(config, max_degree, G.number_of_nodes()); - tabu_moves_queue* tabu_moves = new tabu_moves_queue(); + std::pair< NodeID, PartitionID > undo_move; + undo_move.first = node; + undo_move.second = G.getPartitionIndex(node); + + undo_buffer.push_back(undo_move); + round_counter++; + + G.setPartitionIndex(node, block); + + forall_out_edges(G, e, node) { + for( unsigned i = 0; i < config.k; i++) { + if(queue->contains( node, i)) { + if( gamma->get_xy(node, i) == 0) { + queue->deleteNode(node,i); + } else { + queue->changeKey(node, i, gamma->get_xy(node, i) - gamma->get_xy(node, block)); + } + } else { + if(gamma->get_xy(node, i) > 0 && T->get_xy(node, i) < (int)iteration) { + queue->insert(node, i, gamma->get_xy(node, i) - gamma->get_xy(node, block)); + } + } + } + } endfor + + cur_cut -= gain; + } + + + unsigned tenure = config.maxT;//random_functions::nextInt( config.maxT, 2*config.maxT); + tenure = compute_tenure(iteration, tenure); + unsigned small_offset = random_functions::nextInt(1,3); + T->set_xy(node, block, iteration + tenure + small_offset); + tabu_moves->insert(node, block, iteration + tenure + small_offset); + if( T->get_xy( node, from) < (int)iteration ) { + T->set_xy(node, from, iteration + tenure); + tabu_moves->insert(node, from, iteration + tenure); + } + + if(queue->contains(node, from) ) { + queue->deleteNode(node, from); + } - matrix* T = new normal_matrix(G.number_of_nodes(), config.k); - matrix* gamma = new normal_matrix(G.number_of_nodes(), config.k); + if(queue->contains(node, block) ) { + queue->deleteNode(node, block); + } + } + + //update the best cut found + if( cur_cut < best_cut ) { + best_idx = undo_buffer.size() - 1 ; + best_cut = cur_cut; + no_impro_iterations = 0; + } else { + no_impro_iterations++; + } + + + if( round_counter >= (int)G.number_of_edges() ) { + + if( best_idx != -1 ) { forall_nodes(G, node) { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - PartitionID target_block = G.getPartitionIndex(target); - gamma->set_xy( node, target_block, gamma->get_xy(node, target_block) + 1); - } endfor + cur_state[node] = G.getPartitionIndex(node); } endfor + for( int idx = undo_buffer.size()-1; idx > best_idx; idx--) { + G.setPartitionIndex( undo_buffer[idx].first, undo_buffer[idx].second ); + } forall_nodes(G, node) { - bool is_bnd = false; - PartitionID pIdx = G.getPartitionIndex(node); - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if( pIdx != G.getPartitionIndex(target)) { - is_bnd = true; - break; - } - } endfor - - if(is_bnd) { - for( unsigned block = 0; block < config.k; block++) { - if( gamma->get_xy(node, block) > 0 && G.getPartitionIndex(node) != block) { - queue->insert(node, block, gamma->get_xy(node, block) - gamma->get_xy(node, G.getPartitionIndex(node))); - } else { - tabu_moves->insert(node, block, 0); - } - } - } + bestmap[node] = G.getPartitionIndex(node); + G.setPartitionIndex(node, cur_state[node]); } endfor - - unsigned no_impro_iterations = 0; - config.maxT = random_functions::nextInt(50, 3000); - unsigned iteration_limit = std::min((int)(2*G.number_of_nodes()), 40000); - - std::vector< std::pair > undo_buffer; - undo_buffer.reserve(G.number_of_edges()); - - std::vector cur_state(G.number_of_nodes()); - int best_idx = -1; int round_counter = -1; unsigned iteration = 0; - - for( iteration = 0, round_counter = 0; iteration < config.maxIter; iteration++) { - if(!queue->empty()) { - Gain gain = queue->maxValue(); - std::pair< NodeID, PartitionID > p = queue->deleteMax(); - NodeID node = p.first; - NodeID block = p.second; - NodeID from = G.getPartitionIndex(node); - - if( boundary.getBlockWeight(block) + 1 < config.upper_bound_partition && from != block) { - boundary.setBlockWeight(from, boundary.getBlockWeight(from) - 1); - boundary.setBlockWeight(block, boundary.getBlockWeight(block) + 1); - - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - gamma->set_xy( target, from, gamma->get_xy(target, from) - 1); - gamma->set_xy( target, block, gamma->get_xy(target, block) + 1); - } endfor - - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - PartitionID target_block = G.getPartitionIndex(target); - - for( unsigned i = 0; i < config.k; i++) { - if(queue->contains( target, i )) { - if( gamma->get_xy(target, i) == 0) { - queue->deleteNode(target, i); - } else { - queue->changeKey(target, i, gamma->get_xy(target, i) - gamma->get_xy(target, target_block)); - } - } else { - if( gamma->get_xy(target, i) > 0 && T->get_xy(target, i) < (int)iteration) { - queue->insert(target, i, gamma->get_xy(target, i) - gamma->get_xy(target, target_block)); - } - } - } - } endfor - - std::pair< NodeID, PartitionID > undo_move; - undo_move.first = node; - undo_move.second = G.getPartitionIndex(node); - - undo_buffer.push_back(undo_move); - round_counter++; - - G.setPartitionIndex(node, block); - - forall_out_edges(G, e, node) { - for( unsigned i = 0; i < config.k; i++) { - if(queue->contains( node, i)) { - if( gamma->get_xy(node, i) == 0) { - queue->deleteNode(node,i); - } else { - queue->changeKey(node, i, gamma->get_xy(node, i) - gamma->get_xy(node, block)); - } - } else { - if(gamma->get_xy(node, i) > 0 && T->get_xy(node, i) < (int)iteration) { - queue->insert(node, i, gamma->get_xy(node, i) - gamma->get_xy(node, block)); - } - } - } - } endfor - - cur_cut -= gain; - } - - - unsigned tenure = config.maxT;//random_functions::nextInt( config.maxT, 2*config.maxT); - tenure = compute_tenure(iteration, tenure); - unsigned small_offset = random_functions::nextInt(1,3); - T->set_xy(node, block, iteration + tenure + small_offset); - tabu_moves->insert(node, block, iteration + tenure + small_offset); - if( T->get_xy( node, from) < (int)iteration ) { - T->set_xy(node, from, iteration + tenure); - tabu_moves->insert(node, from, iteration + tenure); - } - - if(queue->contains(node, from) ) { - queue->deleteNode(node, from); - } - - if(queue->contains(node, block) ) { - queue->deleteNode(node, block); - } - - } - - //update the best cut found - if( cur_cut < best_cut ) { - best_idx = undo_buffer.size() - 1 ; - best_cut = cur_cut; - no_impro_iterations = 0; - } else { - no_impro_iterations++; - } - - - if( round_counter >= (int)G.number_of_edges() ) { - - if( best_idx != -1 ) { - forall_nodes(G, node) { - cur_state[node] = G.getPartitionIndex(node); - } endfor - - for( int idx = undo_buffer.size()-1; idx > best_idx; idx--) { - G.setPartitionIndex( undo_buffer[idx].first, undo_buffer[idx].second ); - } - forall_nodes(G, node) { - bestmap[node] = G.getPartitionIndex(node); - G.setPartitionIndex(node, cur_state[node]); - } endfor - } - undo_buffer.clear(); - best_idx = -1; - round_counter = -1; - } - - if(no_impro_iterations > iteration_limit) { - break; - } - - //reinsert the buffer - if( !tabu_moves->empty() ) { - while( tabu_moves->minValue() <= (int)iteration ) { - std::pair< NodeID, PartitionID > p = tabu_moves->deleteMin(); - NodeID node = p.first; - NodeID block = p.second; - - if( block == G.getPartitionIndex(node) ) { - unsigned tenure = compute_tenure(iteration, config.maxT); - T->set_xy(node, block, iteration + tenure); - tabu_moves->insert(node, block,iteration + tenure); - } else { - if(gamma->get_xy(node, block) > 0) { - queue->insert( p.first, p.second, gamma->get_xy(node, block) - gamma->get_xy(node, G.getPartitionIndex(node))); - } - } - } - } +} + undo_buffer.clear(); + best_idx = -1; + round_counter = -1; + } + + if(no_impro_iterations > iteration_limit) { + break; + } + + //reinsert the buffer + if( !tabu_moves->empty() ) { + while( tabu_moves->minValue() <= (int)iteration ) { + std::pair< NodeID, PartitionID > p = tabu_moves->deleteMin(); + NodeID node = p.first; + NodeID block = p.second; + + if( block == G.getPartitionIndex(node) ) { + unsigned tenure = compute_tenure(iteration, config.maxT); + T->set_xy(node, block, iteration + tenure); + tabu_moves->insert(node, block,iteration + tenure); + } else { + if(gamma->get_xy(node, block) > 0) { + queue->insert( p.first, p.second, gamma->get_xy(node, block) - gamma->get_xy(node, G.getPartitionIndex(node))); + } } - if( best_idx != -1 ) { - forall_nodes(G, node) { - cur_state[node] = G.getPartitionIndex(node); - } endfor + } + } + } + if( best_idx != -1 ) { + forall_nodes(G, node) { + cur_state[node] = G.getPartitionIndex(node); + } endfor - for( int idx = undo_buffer.size()-1; idx > best_idx; idx--) { - G.setPartitionIndex( undo_buffer[idx].first, undo_buffer[idx].second ); - } + for( int idx = undo_buffer.size()-1; idx > best_idx; idx--) { + G.setPartitionIndex( undo_buffer[idx].first, undo_buffer[idx].second ); + } - forall_nodes(G, node) { - bestmap[node] = G.getPartitionIndex(node); - G.setPartitionIndex(node, cur_state[node]); - } endfor + forall_nodes(G, node) { + bestmap[node] = G.getPartitionIndex(node); + G.setPartitionIndex(node, cur_state[node]); + } endfor - } +} - forall_nodes(G, node) { - G.setPartitionIndex(node, bestmap[node]); - } endfor + forall_nodes(G, node) { + G.setPartitionIndex(node, bestmap[node]); + } endfor - delete T; - delete gamma; - delete queue; - delete tabu_moves; + delete T; + delete gamma; + delete queue; + delete tabu_moves; - return 0; + return 0; } +} \ No newline at end of file diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/tabu_search/tabu_search.h b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/tabu_search/tabu_search.h index c61c36a1..e70ef2dc 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/refinement/tabu_search/tabu_search.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/refinement/tabu_search/tabu_search.h @@ -13,54 +13,54 @@ #include "definitions.h" #include "uncoarsening/refinement/kway_graph_refinement/kway_graph_refinement_commons.h" #include "uncoarsening/refinement/refinement.h" - +namespace kahip::modified { class tabu_search : public refinement { - public: - tabu_search(); - virtual ~tabu_search(); +public: + tabu_search(); + virtual ~tabu_search(); - virtual EdgeWeight perform_refinement(PartitionConfig & config, - graph_access & G, - complete_boundary & boundary); + virtual EdgeWeight perform_refinement(PartitionConfig & config, + graph_access & G, + complete_boundary & boundary); - private: - unsigned compute_tenure(unsigned iteration, unsigned max_iteration) { - std::vector< double > b(15,0); - b[0] = 1/8.0; - b[1] = 2/8.0; - b[2] = 1/8.0; - b[3] = 4/8.0; - b[4] = 1/8.0; - b[5] = 2/8.0; - b[6] = 1/8.0; - b[7] = 8/8.0; - b[8] = 1/8.0; - b[9] = 2/8.0; - b[10] = 1/8.0; - b[11] = 4/8.0; - b[12] = 1/8.0; - b[13] = 2/8.0; - b[14] = 1/8.0; +private: + unsigned compute_tenure(unsigned iteration, unsigned max_iteration) { + std::vector< double > b(15,0); + b[0] = 1/8.0; + b[1] = 2/8.0; + b[2] = 1/8.0; + b[3] = 4/8.0; + b[4] = 1/8.0; + b[5] = 2/8.0; + b[6] = 1/8.0; + b[7] = 8/8.0; + b[8] = 1/8.0; + b[9] = 2/8.0; + b[10] = 1/8.0; + b[11] = 4/8.0; + b[12] = 1/8.0; + b[13] = 2/8.0; + b[14] = 1/8.0; - //compute i - unsigned i = 1; - unsigned x = 4*max_iteration*b[0]; - while( true ) { - if( iteration >= x ) { - x = x + 4*max_iteration*b[i%15]; - i++; - } else { - i--; - break; - } - } - return max_iteration*b[i%15]; - + //compute i + unsigned i = 1; + unsigned x = 4*max_iteration*b[0]; + while( true ) { + if( iteration >= x ) { + x = x + 4*max_iteration*b[i%15]; + i++; + } else { + i--; + break; + } } + return max_iteration*b[i%15]; - kway_graph_refinement_commons* commons; - matrix* m; -}; + } + kway_graph_refinement_commons* commons; + matrix* m; +}; +} #endif /* end of include guard: TABU_SEARCH_RC6W8GGX */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/separator/vertex_separator_algorithm.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/separator/vertex_separator_algorithm.cpp index 83de2245..a132aaec 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/separator/vertex_separator_algorithm.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/separator/vertex_separator_algorithm.cpp @@ -11,7 +11,7 @@ #include "uncoarsening/refinement/quotient_graph_refinement/quotient_graph_scheduling/simple_quotient_graph_scheduler.h" #include "vertex_separator_algorithm.h" #include "vertex_separator_flow_solver.h" - +namespace kahip::modified { vertex_separator_algorithm::vertex_separator_algorithm() { } @@ -25,135 +25,136 @@ void vertex_separator_algorithm::compute_vertex_separator(const PartitionConfig complete_boundary & boundary, std::vector & overall_separator) { - PartitionConfig cfg = config; - cfg.bank_account_factor = 1; + PartitionConfig cfg = config; + cfg.bank_account_factor = 1; + + QuotientGraphEdges qgraph_edges; + boundary.getQuotientGraphEdges(qgraph_edges); + + quotient_graph_scheduling* scheduler = new simple_quotient_graph_scheduler(cfg, qgraph_edges,qgraph_edges.size()); + + std::unordered_map allready_separator; + do { + boundary_pair & bp = scheduler->getNext(); + PartitionID lhs = bp.lhs; + PartitionID rhs = bp.rhs; + + boundary_starting_nodes start_nodes_lhs; + boundary_starting_nodes start_nodes_rhs; + + PartialBoundary & lhs_b = boundary.getDirectedBoundary(lhs, lhs, rhs); + PartialBoundary & rhs_b = boundary.getDirectedBoundary(rhs, lhs, rhs); + + forall_boundary_nodes(lhs_b, cur_bnd_node) { + if(allready_separator.find(cur_bnd_node) == allready_separator.end()) { + start_nodes_lhs.push_back(cur_bnd_node); + } + } endfor + + forall_boundary_nodes(rhs_b, cur_bnd_node) { + if(allready_separator.find(cur_bnd_node) == allready_separator.end()) { + start_nodes_rhs.push_back(cur_bnd_node); + } + } endfor + + vertex_separator_flow_solver vsfs; + std::vector separator; + vsfs.find_separator(config, G, lhs, rhs, start_nodes_lhs, start_nodes_rhs, separator); + for( unsigned i = 0; i < separator.size(); i++) { + allready_separator[separator[i]] = true; + } + //*************************** end **************************************** + } while(!scheduler->hasFinished()); + + + // now print the computed vertex separator to disk + std::unordered_map::iterator it; + for( it = allready_separator.begin(); it != allready_separator.end(); ++it) { + overall_separator.push_back(it->first); + } + is_vertex_separator(G, allready_separator); +} + +void vertex_separator_algorithm::compute_vertex_separator(const PartitionConfig & config, + graph_access & G, + complete_boundary & boundary) { - QuotientGraphEdges qgraph_edges; - boundary.getQuotientGraphEdges(qgraph_edges); + std::vector overall_separator; + compute_vertex_separator(config, G, boundary, overall_separator); - quotient_graph_scheduling* scheduler = new simple_quotient_graph_scheduler(cfg, qgraph_edges,qgraph_edges.size()); + // write the partition to the disc + std::stringstream filename; + filename << "tmpseparator" << config.k; + graph_io::writeVector(overall_separator, filename.str()); +} - std::unordered_map allready_separator; - do { - boundary_pair & bp = scheduler->getNext(); - PartitionID lhs = bp.lhs; - PartitionID rhs = bp.rhs; +void vertex_separator_algorithm::compute_vertex_separator_simple(const PartitionConfig & config, + graph_access & G, + complete_boundary & boundary, + std::vector & overall_separator) { - boundary_starting_nodes start_nodes_lhs; - boundary_starting_nodes start_nodes_rhs; + PartitionConfig cfg = config; + cfg.bank_account_factor = 1; - PartialBoundary & lhs_b = boundary.getDirectedBoundary(lhs, lhs, rhs); - PartialBoundary & rhs_b = boundary.getDirectedBoundary(rhs, lhs, rhs); + QuotientGraphEdges qgraph_edges; + boundary.getQuotientGraphEdges(qgraph_edges); - forall_boundary_nodes(lhs_b, cur_bnd_node) { - if(allready_separator.find(cur_bnd_node) == allready_separator.end()) { - start_nodes_lhs.push_back(cur_bnd_node); - } - } endfor + quotient_graph_scheduling* scheduler = new simple_quotient_graph_scheduler(cfg, qgraph_edges,qgraph_edges.size()); - forall_boundary_nodes(rhs_b, cur_bnd_node) { - if(allready_separator.find(cur_bnd_node) == allready_separator.end()) { - start_nodes_rhs.push_back(cur_bnd_node); - } - } endfor + std::unordered_map allready_separator; + do { + boundary_pair & bp = scheduler->getNext(); + PartitionID lhs = bp.lhs; + PartitionID rhs = bp.rhs; - vertex_separator_flow_solver vsfs; - std::vector separator; - vsfs.find_separator(config, G, lhs, rhs, start_nodes_lhs, start_nodes_rhs, separator); - for( unsigned i = 0; i < separator.size(); i++) { - allready_separator[separator[i]] = true; - } - //*************************** end **************************************** - } while(!scheduler->hasFinished()); + boundary_starting_nodes start_nodes_lhs; + boundary_starting_nodes start_nodes_rhs; + PartialBoundary & lhs_b = boundary.getDirectedBoundary(lhs, lhs, rhs); + PartialBoundary & rhs_b = boundary.getDirectedBoundary(rhs, lhs, rhs); - // now print the computed vertex separator to disk - std::unordered_map::iterator it; - for( it = allready_separator.begin(); it != allready_separator.end(); ++it) { - overall_separator.push_back(it->first); + if(lhs_b.size() < rhs_b.size()) { + forall_boundary_nodes(lhs_b, cur_bnd_node) { + if(allready_separator.find(cur_bnd_node) == allready_separator.end()) { + //overall_separator.push_back(cur_bnd_node); + allready_separator[cur_bnd_node] = true; } - is_vertex_separator(G, allready_separator); + } endfor +} else { + forall_boundary_nodes(rhs_b, cur_bnd_node) { + if(allready_separator.find(cur_bnd_node) == allready_separator.end()) { + //overall_separator.push_back(cur_bnd_node); + allready_separator[cur_bnd_node] = true; + } + } endfor } -void vertex_separator_algorithm::compute_vertex_separator(const PartitionConfig & config, - graph_access & G, - complete_boundary & boundary) { - - std::vector overall_separator; - compute_vertex_separator(config, G, boundary, overall_separator); - - // write the partition to the disc - std::stringstream filename; - filename << "tmpseparator" << config.k; - graph_io::writeVector(overall_separator, filename.str()); -} + //*************************** end **************************************** + } while(!scheduler->hasFinished()); -void vertex_separator_algorithm::compute_vertex_separator_simple(const PartitionConfig & config, - graph_access & G, - complete_boundary & boundary, - std::vector & overall_separator) { - PartitionConfig cfg = config; - cfg.bank_account_factor = 1; - - QuotientGraphEdges qgraph_edges; - boundary.getQuotientGraphEdges(qgraph_edges); - - quotient_graph_scheduling* scheduler = new simple_quotient_graph_scheduler(cfg, qgraph_edges,qgraph_edges.size()); - - std::unordered_map allready_separator; - do { - boundary_pair & bp = scheduler->getNext(); - PartitionID lhs = bp.lhs; - PartitionID rhs = bp.rhs; - - boundary_starting_nodes start_nodes_lhs; - boundary_starting_nodes start_nodes_rhs; - - PartialBoundary & lhs_b = boundary.getDirectedBoundary(lhs, lhs, rhs); - PartialBoundary & rhs_b = boundary.getDirectedBoundary(rhs, lhs, rhs); - - if(lhs_b.size() < rhs_b.size()) { - forall_boundary_nodes(lhs_b, cur_bnd_node) { - if(allready_separator.find(cur_bnd_node) == allready_separator.end()) { - //overall_separator.push_back(cur_bnd_node); - allready_separator[cur_bnd_node] = true; - } - } endfor - } else { - forall_boundary_nodes(rhs_b, cur_bnd_node) { - if(allready_separator.find(cur_bnd_node) == allready_separator.end()) { - //overall_separator.push_back(cur_bnd_node); - allready_separator[cur_bnd_node] = true; - } - } endfor - } - - //*************************** end **************************************** - } while(!scheduler->hasFinished()); - - - // now print the computed vertex separator to disk - std::unordered_map::iterator it; - for( it = allready_separator.begin(); it != allready_separator.end(); ++it) { - overall_separator.push_back(it->first); - } - is_vertex_separator(G, allready_separator); + // now print the computed vertex separator to disk + std::unordered_map::iterator it; + for( it = allready_separator.begin(); it != allready_separator.end(); ++it) { + overall_separator.push_back(it->first); + } + is_vertex_separator(G, allready_separator); } bool vertex_separator_algorithm::is_vertex_separator(graph_access & G, std::unordered_map & separator) { - forall_nodes(G, node) { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if( G.getPartitionIndex(node) != G.getPartitionIndex(target)) { - // in this case one of them has to be a separator - if( separator.find(node) == separator.end() && - separator.find(target) == separator.end()) { - std::cout << "not a separator!" << std::endl; - ASSERT_TRUE(false); - } - } - } endfor - } endfor - return true; + forall_nodes(G, node) { + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + if( G.getPartitionIndex(node) != G.getPartitionIndex(target)) { + // in this case one of them has to be a separator + if( separator.find(node) == separator.end() && + separator.find(target) == separator.end()) { + std::cout << "not a separator!" << std::endl; + ASSERT_TRUE(false); + } + } + } endfor +} endfor +return true; } +} \ No newline at end of file diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/separator/vertex_separator_algorithm.h b/parallel/modified_kahip/lib/partition/uncoarsening/separator/vertex_separator_algorithm.h index a066ca8c..cda4028c 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/separator/vertex_separator_algorithm.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/separator/vertex_separator_algorithm.h @@ -13,30 +13,30 @@ #include "data_structure/graph_access.h" #include "partition_config.h" #include "uncoarsening/refinement/quotient_graph_refinement/complete_boundary.h" - +namespace kahip::modified { class vertex_separator_algorithm { - public: - vertex_separator_algorithm(); - virtual ~vertex_separator_algorithm(); +public: + vertex_separator_algorithm(); + virtual ~vertex_separator_algorithm(); - void compute_vertex_separator(const PartitionConfig & config, - graph_access & G, - complete_boundary & boundary, - std::vector & overall_separator); + void compute_vertex_separator(const PartitionConfig & config, + graph_access & G, + complete_boundary & boundary, + std::vector & overall_separator); - void compute_vertex_separator_simple(const PartitionConfig & config, - graph_access & G, - complete_boundary & boundary, - std::vector & overall_separator); + void compute_vertex_separator_simple(const PartitionConfig & config, + graph_access & G, + complete_boundary & boundary, + std::vector & overall_separator); - void compute_vertex_separator(const PartitionConfig & config, - graph_access & G, - complete_boundary & boundary); + void compute_vertex_separator(const PartitionConfig & config, + graph_access & G, + complete_boundary & boundary); - //ASSERTIONS - bool is_vertex_separator(graph_access & G, std::unordered_map & separator); + //ASSERTIONS + bool is_vertex_separator(graph_access & G, std::unordered_map & separator); }; - +} #endif /* end of include guard: VERTEX_SEPARTATOR_ALGORITHM_XUDNZZM8 */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/separator/vertex_separator_flow_solver.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/separator/vertex_separator_flow_solver.cpp index cd435da3..cb53efe2 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/separator/vertex_separator_flow_solver.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/separator/vertex_separator_flow_solver.cpp @@ -12,7 +12,7 @@ #include "vertex_separator_flow_solver.h" #include "flow_solving_kernel/flow_macros.h" - +namespace kahip::modified { vertex_separator_flow_solver::vertex_separator_flow_solver() { } @@ -29,191 +29,191 @@ void vertex_separator_flow_solver::find_separator(const PartitionConfig & config boundary_starting_nodes rhs_nodes, std::vector & separator) { - if(lhs_nodes.size() == 0 || rhs_nodes.size() == 0) return; - - node *j = NULL; - int cc; - bucket *l; - - - globUpdtFreq = GLOB_UPDT_FREQ; - std::vector new_to_old_ids; - - EdgeID no_edges_in_flow_graph = 0; - bool success = construct_flow_pb(config, G, lhs, rhs, lhs_nodes, rhs_nodes, new_to_old_ids, - &n, - &m, - &nodes, - &arcs, - &cap, - &source, - &sink, - &nMin, - no_edges_in_flow_graph ); - - - cc = internal_allocDS(); - if(!success) return; - if ( cc ) { fprintf ( stderr, "Allocation error\n"); exit ( 1 ); } - - internal_init(); - internal_stage_one( ); - - internal_stage_two(); - - /* check if mincut is saturated */ - aMax = dMax = 0; - for (l = buckets; l < buckets + n; l++) { - l->firstActive = sentinelNode; - l->firstInactive = sentinelNode; - } - internal_global_update(); - - std::vector S; - forAllNodes(j) { - if (!(j->d < n)) { - if((unsigned) (nNode(j) - 1) < (int)lhs_nodes.size() + rhs_nodes.size()) { //Note: unsigned has been introduced without testing - S.push_back(new_to_old_ids[nNode(j) -1]); - } - } - } - - std::sort(lhs_nodes.begin(), lhs_nodes.end()); - std::sort(rhs_nodes.begin(), rhs_nodes.end()); - std::sort(S.begin(), S.end()); - - std::vector separator_tmp(lhs_nodes.size() + rhs_nodes.size(), -1); - std::vector::iterator it; - it = std::set_intersection(rhs_nodes.begin(), rhs_nodes.end(), S.begin(), S.end(), separator_tmp.begin()); - - - for( unsigned i = 0; i < separator_tmp.size(); i++) { - if(separator_tmp[i] != -1) { - separator.push_back(separator_tmp[i]); - } - } - std::vector::iterator it2; - std::vector separator_tmp2(lhs_nodes.size() + rhs_nodes.size(), -1); - it2 = std::set_difference(lhs_nodes.begin(), lhs_nodes.end(), S.begin(), S.end(), separator_tmp2.begin()); - for( unsigned i = 0; i < separator_tmp2.size(); i++) { - if(separator_tmp2[i] != -1) { - separator.push_back(separator_tmp2[i]); - } - } + if(lhs_nodes.size() == 0 || rhs_nodes.size() == 0) return; + + node *j = NULL; + int cc; + bucket *l; + + + globUpdtFreq = GLOB_UPDT_FREQ; + std::vector new_to_old_ids; + + EdgeID no_edges_in_flow_graph = 0; + bool success = construct_flow_pb(config, G, lhs, rhs, lhs_nodes, rhs_nodes, new_to_old_ids, + &n, + &m, + &nodes, + &arcs, + &cap, + &source, + &sink, + &nMin, + no_edges_in_flow_graph ); + + + cc = internal_allocDS(); + if(!success) return; + if ( cc ) { fprintf ( stderr, "Allocation error\n"); exit ( 1 ); } + + internal_init(); + internal_stage_one( ); + + internal_stage_two(); + + /* check if mincut is saturated */ + aMax = dMax = 0; + for (l = buckets; l < buckets + n; l++) { + l->firstActive = sentinelNode; + l->firstInactive = sentinelNode; + } + internal_global_update(); + + std::vector S; + forAllNodes(j) { + if (!(j->d < n)) { + if((unsigned) (nNode(j) - 1) < (int)lhs_nodes.size() + rhs_nodes.size()) { //Note: unsigned has been introduced without testing + S.push_back(new_to_old_ids[nNode(j) -1]); + } + } + } + + std::sort(lhs_nodes.begin(), lhs_nodes.end()); + std::sort(rhs_nodes.begin(), rhs_nodes.end()); + std::sort(S.begin(), S.end()); + + std::vector separator_tmp(lhs_nodes.size() + rhs_nodes.size(), -1); + std::vector::iterator it; + it = std::set_intersection(rhs_nodes.begin(), rhs_nodes.end(), S.begin(), S.end(), separator_tmp.begin()); + + + for( unsigned i = 0; i < separator_tmp.size(); i++) { + if(separator_tmp[i] != -1) { + separator.push_back(separator_tmp[i]); + } + } + std::vector::iterator it2; + std::vector separator_tmp2(lhs_nodes.size() + rhs_nodes.size(), -1); + it2 = std::set_difference(lhs_nodes.begin(), lhs_nodes.end(), S.begin(), S.end(), separator_tmp2.begin()); + for( unsigned i = 0; i < separator_tmp2.size(); i++) { + if(separator_tmp2[i] != -1) { + separator.push_back(separator_tmp2[i]); + } + } } -bool vertex_separator_flow_solver::construct_flow_pb( const PartitionConfig & config, - graph_access & G, - PartitionID & lhs, - PartitionID & rhs, +bool vertex_separator_flow_solver::construct_flow_pb( const PartitionConfig & config, + graph_access & G, + PartitionID & lhs, + PartitionID & rhs, std::vector & lhs_nodes, std::vector & rhs_nodes, - std::vector & new_to_old_ids, - long *n_ad, - long* m_ad, - node** nodes_ad, - arc** arcs_ad, + std::vector & new_to_old_ids, + long *n_ad, + long* m_ad, + node** nodes_ad, + arc** arcs_ad, long ** cap_ad, - node** source_ad, - node** sink_ad, + node** source_ad, + node** sink_ad, long* node_min_ad, - EdgeID & no_edges_in_flow_graph) { - - //very dirty for loading variables :). some time this should all be refactored. for now we can focus on the important stuff. - #include "../refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/convert_ds_variables.h" - - //building up the graph as in parse.h of hi_pr code - //first we have to count the number of edges - // s to lhs + rhs to t + lhs to rhs - unsigned no_edges = 0; - for( unsigned i = 0; i < lhs_nodes.size(); i++) { - NodeID node = lhs_nodes[i]; - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if(rhs == G.getPartitionIndex(target)) { - ++no_edges; - } - } endfor - } - - //build mappings from old to new node ids and reverse - NodeID idx = 0; - new_to_old_ids.resize(lhs_nodes.size() + rhs_nodes.size()); - std::unordered_map old_to_new; - for( unsigned i = 0; i < lhs_nodes.size(); i++) { - new_to_old_ids[idx] = lhs_nodes[i]; - old_to_new[lhs_nodes[i]] = idx++ ; - } - for( unsigned i = 0; i < rhs_nodes.size(); i++) { - new_to_old_ids[idx] = rhs_nodes[i]; - old_to_new[rhs_nodes[i]] = idx++; - } - - n = lhs_nodes.size() + rhs_nodes.size() + 2; //+source and target - m = no_edges + lhs_nodes.size() + rhs_nodes.size(); - - nodes = (node*) calloc ( n+2, sizeof(node) ); - arcs = (arc*) calloc ( 2*m+1, sizeof(arc) ); - arc_tail = (long*) calloc ( 2*m, sizeof(long) ); - arc_first= (long*) calloc ( n+2, sizeof(long) ); - acap = (long*) calloc ( 2*m, sizeof(long) ); - arc_current = arcs; - - node_max = 0; - node_min = n; - - if(n == 2) return false; - - unsigned nodeoffset = 1; - source = n - 2 + nodeoffset; - sink = source+1; - - idx = 0; - long max_capacity = std::numeric_limits::max(); - //insert directed edges from L to R - for( unsigned i = 0; i < lhs_nodes.size(); i++, idx++) { - NodeID node = lhs_nodes[i]; - NodeID sourceID = idx + nodeoffset; - forall_out_edges(G, e, node) { - if(G.getPartitionIndex(G.getEdgeTarget(e)) == rhs) { - NodeID targetID = old_to_new[G.getEdgeTarget(e)] + nodeoffset; - tail = sourceID; - head = targetID; - cap = max_capacity; - - createEdge() - } - } endfor - } - - //connect source and target with outer boundary nodes - for(unsigned i = 0; i < lhs_nodes.size(); i++) { - NodeID targetID = old_to_new[lhs_nodes[i]]+nodeoffset; - tail = source; - head = targetID; - cap = G.getNodeWeight(lhs_nodes[i]); - - createEdge() - } - - for(unsigned i = 0; i < rhs_nodes.size(); i++) { - NodeID sourceID = old_to_new[rhs_nodes[i]]+ nodeoffset; - tail = sourceID; - head = sink; - cap = G.getNodeWeight(rhs_nodes[i]); - - createEdge() - } - - //very dirty - #include "../refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/linear_ordering_n_assign.h" - /* Thanks God! all is done */ - - return true; + EdgeID & no_edges_in_flow_graph) { + + //very dirty for loading variables :). some time this should all be refactored. for now we can focus on the important stuff. +#include "../refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/convert_ds_variables.h" + + //building up the graph as in parse.h of hi_pr code + //first we have to count the number of edges + // s to lhs + rhs to t + lhs to rhs + unsigned no_edges = 0; + for( unsigned i = 0; i < lhs_nodes.size(); i++) { + NodeID node = lhs_nodes[i]; + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + if(rhs == G.getPartitionIndex(target)) { + ++no_edges; + } + } endfor +} + //build mappings from old to new node ids and reverse + NodeID idx = 0; + new_to_old_ids.resize(lhs_nodes.size() + rhs_nodes.size()); + std::unordered_map old_to_new; + for( unsigned i = 0; i < lhs_nodes.size(); i++) { + new_to_old_ids[idx] = lhs_nodes[i]; + old_to_new[lhs_nodes[i]] = idx++ ; + } + for( unsigned i = 0; i < rhs_nodes.size(); i++) { + new_to_old_ids[idx] = rhs_nodes[i]; + old_to_new[rhs_nodes[i]] = idx++; + } + + n = lhs_nodes.size() + rhs_nodes.size() + 2; //+source and target + m = no_edges + lhs_nodes.size() + rhs_nodes.size(); + + nodes = (node*) calloc ( n+2, sizeof(node) ); + arcs = (arc*) calloc ( 2*m+1, sizeof(arc) ); + arc_tail = (long*) calloc ( 2*m, sizeof(long) ); + arc_first= (long*) calloc ( n+2, sizeof(long) ); + acap = (long*) calloc ( 2*m, sizeof(long) ); + arc_current = arcs; + + node_max = 0; + node_min = n; + + if(n == 2) return false; + + unsigned nodeoffset = 1; + source = n - 2 + nodeoffset; + sink = source+1; + + idx = 0; + long max_capacity = std::numeric_limits::max(); + //insert directed edges from L to R + for( unsigned i = 0; i < lhs_nodes.size(); i++, idx++) { + NodeID node = lhs_nodes[i]; + NodeID sourceID = idx + nodeoffset; + forall_out_edges(G, e, node) { + if(G.getPartitionIndex(G.getEdgeTarget(e)) == rhs) { + NodeID targetID = old_to_new[G.getEdgeTarget(e)] + nodeoffset; + tail = sourceID; + head = targetID; + cap = max_capacity; + + createEdge() +} + } endfor +} + + //connect source and target with outer boundary nodes + for(unsigned i = 0; i < lhs_nodes.size(); i++) { + NodeID targetID = old_to_new[lhs_nodes[i]]+nodeoffset; + tail = source; + head = targetID; + cap = G.getNodeWeight(lhs_nodes[i]); + + createEdge() +} + for(unsigned i = 0; i < rhs_nodes.size(); i++) { + NodeID sourceID = old_to_new[rhs_nodes[i]]+ nodeoffset; + tail = sourceID; + head = sink; + cap = G.getNodeWeight(rhs_nodes[i]); + createEdge() } + //very dirty +#include "../refinement/quotient_graph_refinement/flow_refinement/flow_solving_kernel/linear_ordering_n_assign.h" + /* Thanks God! all is done */ + + return true; + + + +} +} diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/separator/vertex_separator_flow_solver.h b/parallel/modified_kahip/lib/partition/uncoarsening/separator/vertex_separator_flow_solver.h index 49b0e947..b9562073 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/separator/vertex_separator_flow_solver.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/separator/vertex_separator_flow_solver.h @@ -9,39 +9,39 @@ #define VERTEX_SEPARATOR_FLOW_SOLVER_FLA4518Q #include "flow_solving_kernel/flow_solver.h" - +namespace kahip::modified { class vertex_separator_flow_solver : public flow_solver { public: - vertex_separator_flow_solver(); - virtual ~vertex_separator_flow_solver(); - - bool construct_flow_pb( const PartitionConfig & config, - graph_access & G, - PartitionID & lhs, - PartitionID & rhs, - std::vector & lhs_boundary_stripe, - std::vector & rhs_boundary_stripe, - std::vector & new_to_old_ids, - long *n_ad, - long* m_ad, - node** nodes_ad, - arc** arcs_ad, - long ** cap_ad, - node** source_ad, - node** sink_ad, - long* node_min_ad, - EdgeID & no_edges_in_flow_graph); - - - void find_separator(const PartitionConfig & config, - graph_access & G, - PartitionID lhs, - PartitionID rhs, - boundary_starting_nodes start_nodes_lhs, - boundary_starting_nodes start_nodes_rhs, - std::vector & separator); + vertex_separator_flow_solver(); + virtual ~vertex_separator_flow_solver(); + + bool construct_flow_pb( const PartitionConfig & config, + graph_access & G, + PartitionID & lhs, + PartitionID & rhs, + std::vector & lhs_boundary_stripe, + std::vector & rhs_boundary_stripe, + std::vector & new_to_old_ids, + long *n_ad, + long* m_ad, + node** nodes_ad, + arc** arcs_ad, + long ** cap_ad, + node** source_ad, + node** sink_ad, + long* node_min_ad, + EdgeID & no_edges_in_flow_graph); + + + void find_separator(const PartitionConfig & config, + graph_access & G, + PartitionID lhs, + PartitionID rhs, + boundary_starting_nodes start_nodes_lhs, + boundary_starting_nodes start_nodes_rhs, + std::vector & separator); }; - +} #endif /* end of include guard: VERTEX_SEPARATOR_FLOW_SOLVER_FLA4518Q */ diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/uncoarsening.cpp b/parallel/modified_kahip/lib/partition/uncoarsening/uncoarsening.cpp index 978a292c..b6780573 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/uncoarsening.cpp +++ b/parallel/modified_kahip/lib/partition/uncoarsening/uncoarsening.cpp @@ -13,8 +13,7 @@ #include "refinement/refinement.h" #include "separator/vertex_separator_algorithm.h" #include "uncoarsening.h" - - +namespace kahip::modified { uncoarsening::uncoarsening() { } @@ -24,83 +23,83 @@ uncoarsening::~uncoarsening() { } int uncoarsening::perform_uncoarsening(const PartitionConfig & config, graph_hierarchy & hierarchy) { - int improvement = 0; - - PartitionConfig cfg = config; - refinement* refine = NULL; - - if(config.label_propagation_refinement) { - refine = new label_propagation_refinement(); - } else { - refine = new mixed_refinement(); - } - - graph_access * coarsest = hierarchy.get_coarsest(); - PRINT(std::cout << "log>" << "unrolling graph with " << coarsest->number_of_nodes() << std::endl;) - - complete_boundary* finer_boundary = NULL; - complete_boundary* coarser_boundary = NULL; - if(!config.label_propagation_refinement) { - coarser_boundary = new complete_boundary(coarsest); - coarser_boundary->build(); - } - double factor = config.balance_factor; - cfg.upper_bound_partition = ((!hierarchy.isEmpty()) * factor +1.0)*config.upper_bound_partition; - improvement += (int)refine->perform_refinement(cfg, *coarsest, *coarser_boundary); - - NodeID coarser_no_nodes = coarsest->number_of_nodes(); - graph_access* finest = NULL; - graph_access* to_delete = NULL; - unsigned int hierarchy_deepth = hierarchy.size(); - - while(!hierarchy.isEmpty()) { - graph_access* G = hierarchy.pop_finer_and_project(); - - PRINT(std::cout << "log>" << "unrolling graph with " << G->number_of_nodes()<< std::endl;) - - if(!config.label_propagation_refinement) { - finer_boundary = new complete_boundary(G); - finer_boundary->build_from_coarser(coarser_boundary, coarser_no_nodes, hierarchy.get_mapping_of_current_finer()); - } - - //call refinement - double cur_factor = factor/(hierarchy_deepth-hierarchy.size()); - cfg.upper_bound_partition = ((!hierarchy.isEmpty()) * cur_factor+1.0)*config.upper_bound_partition; - PRINT(std::cout << "cfg upperbound " << cfg.upper_bound_partition << std::endl;) - improvement += (int)refine->perform_refinement(cfg, *G, *finer_boundary); - ASSERT_TRUE(graph_partition_assertions::assert_graph_has_kway_partition(config, *G)); - - if(config.use_balance_singletons && !config.label_propagation_refinement) { - finer_boundary->balance_singletons( config, *G ); - } - - // update boundary pointers - if(!config.label_propagation_refinement) delete coarser_boundary; - coarser_boundary = finer_boundary; - coarser_no_nodes = G->number_of_nodes(); - - //clean up - if(to_delete != NULL) { - delete to_delete; - } - if(!hierarchy.isEmpty()) { - to_delete = G; - } - - finest = G; - } - - if(config.compute_vertex_separator) { - PRINT(std::cout << "now computing a vertex separator from the given edge separator" << std::endl;) - vertex_separator_algorithm vsa; - vsa.compute_vertex_separator(config, *finest, *finer_boundary); - } - - delete refine; - if(finer_boundary != NULL) delete finer_boundary; - delete coarsest; - - return improvement; + int improvement = 0; + + PartitionConfig cfg = config; + refinement* refine = NULL; + + if(config.label_propagation_refinement) { + refine = new label_propagation_refinement(); + } else { + refine = new mixed_refinement(); + } + + graph_access * coarsest = hierarchy.get_coarsest(); + PRINT(std::cout << "log>" << "unrolling graph with " << coarsest->number_of_nodes() << std::endl;) + + complete_boundary* finer_boundary = NULL; + complete_boundary* coarser_boundary = NULL; + if(!config.label_propagation_refinement) { + coarser_boundary = new complete_boundary(coarsest); + coarser_boundary->build(); + } + double factor = config.balance_factor; + cfg.upper_bound_partition = ((!hierarchy.isEmpty()) * factor +1.0)*config.upper_bound_partition; + improvement += (int)refine->perform_refinement(cfg, *coarsest, *coarser_boundary); + + NodeID coarser_no_nodes = coarsest->number_of_nodes(); + graph_access* finest = NULL; + graph_access* to_delete = NULL; + unsigned int hierarchy_deepth = hierarchy.size(); + + while(!hierarchy.isEmpty()) { + graph_access* G = hierarchy.pop_finer_and_project(); + + PRINT(std::cout << "log>" << "unrolling graph with " << G->number_of_nodes()<< std::endl;) + + if(!config.label_propagation_refinement) { + finer_boundary = new complete_boundary(G); + finer_boundary->build_from_coarser(coarser_boundary, coarser_no_nodes, hierarchy.get_mapping_of_current_finer()); + } + + //call refinement + double cur_factor = factor/(hierarchy_deepth-hierarchy.size()); + cfg.upper_bound_partition = ((!hierarchy.isEmpty()) * cur_factor+1.0)*config.upper_bound_partition; + PRINT(std::cout << "cfg upperbound " << cfg.upper_bound_partition << std::endl;) + improvement += (int)refine->perform_refinement(cfg, *G, *finer_boundary); + ASSERT_TRUE(graph_partition_assertions::assert_graph_has_kway_partition(config, *G)); + + if(config.use_balance_singletons && !config.label_propagation_refinement) { + finer_boundary->balance_singletons( config, *G ); + } + + // update boundary pointers + if(!config.label_propagation_refinement) delete coarser_boundary; + coarser_boundary = finer_boundary; + coarser_no_nodes = G->number_of_nodes(); + + //clean up + if(to_delete != NULL) { + delete to_delete; + } + if(!hierarchy.isEmpty()) { + to_delete = G; + } + + finest = G; + } + + if(config.compute_vertex_separator) { + PRINT(std::cout << "now computing a vertex separator from the given edge separator" << std::endl;) + vertex_separator_algorithm vsa; + vsa.compute_vertex_separator(config, *finest, *finer_boundary); + } + + delete refine; + if(finer_boundary != NULL) delete finer_boundary; + delete coarsest; + + return improvement; +} } - diff --git a/parallel/modified_kahip/lib/partition/uncoarsening/uncoarsening.h b/parallel/modified_kahip/lib/partition/uncoarsening/uncoarsening.h index c08247d0..69321e8a 100644 --- a/parallel/modified_kahip/lib/partition/uncoarsening/uncoarsening.h +++ b/parallel/modified_kahip/lib/partition/uncoarsening/uncoarsening.h @@ -10,14 +10,14 @@ #include "data_structure/graph_hierarchy.h" #include "partition_config.h" - +namespace kahip::modified { class uncoarsening { public: - uncoarsening( ); - virtual ~uncoarsening(); - - int perform_uncoarsening(const PartitionConfig & config, graph_hierarchy & hierarchy); -}; + uncoarsening( ); + virtual ~uncoarsening(); + int perform_uncoarsening(const PartitionConfig & config, graph_hierarchy & hierarchy); +}; +} #endif /* end of include guard: UNCOARSENING_XSN847F2 */ diff --git a/parallel/modified_kahip/lib/partition/w_cycles/wcycle_partitioner.cpp b/parallel/modified_kahip/lib/partition/w_cycles/wcycle_partitioner.cpp index 41899c89..637edf5f 100644 --- a/parallel/modified_kahip/lib/partition/w_cycles/wcycle_partitioner.cpp +++ b/parallel/modified_kahip/lib/partition/w_cycles/wcycle_partitioner.cpp @@ -21,177 +21,178 @@ #include "uncoarsening/refinement/label_propagation_refinement/label_propagation_refinement.h" #include "uncoarsening/refinement/refinement.h" #include "wcycle_partitioner.h" - +namespace kahip::modified { int wcycle_partitioner::perform_partitioning(const PartitionConfig & config, graph_access & G) { - PartitionConfig cfg = config; + PartitionConfig cfg = config; - if(config.stop_rule == STOP_RULE_SIMPLE) { - m_coarsening_stop_rule = new simple_stop_rule(cfg, G.number_of_nodes()); - } else { - m_coarsening_stop_rule = new multiple_k_stop_rule(cfg, G.number_of_nodes()); - } + if(config.stop_rule == STOP_RULE_SIMPLE) { + m_coarsening_stop_rule = new simple_stop_rule(cfg, G.number_of_nodes()); + } else { + m_coarsening_stop_rule = new multiple_k_stop_rule(cfg, G.number_of_nodes()); + } - int improvement = (int) perform_partitioning_recursive(cfg, G, NULL); - delete m_coarsening_stop_rule; + int improvement = (int) perform_partitioning_recursive(cfg, G, NULL); + delete m_coarsening_stop_rule; - return improvement; + return improvement; } -int wcycle_partitioner::perform_partitioning_recursive( PartitionConfig & partition_config, - graph_access & G, +int wcycle_partitioner::perform_partitioning_recursive( PartitionConfig & partition_config, + graph_access & G, complete_boundary ** c_boundary) { - //if graph not small enough - // perform matching two times - // perform coarsening two times - // call rekursive - //else - // initial partitioning - // - //refinement - NodeID no_of_coarser_vertices = G.number_of_nodes(); - NodeID no_of_finer_vertices = G.number_of_nodes(); - int improvement = 0; - - edge_ratings rating(partition_config); - CoarseMapping* coarse_mapping = new CoarseMapping(); - - graph_access* finer = &G; - matching* edge_matcher = NULL; - contraction* contracter = new contraction(); - PartitionConfig copy_of_partition_config = partition_config; - graph_access* coarser = new graph_access(); - - Matching edge_matching; - NodePermutationMap permutation; - - coarsening_configurator coarsening_config; - coarsening_config.configure_coarsening(partition_config, &edge_matcher, m_level); - - rating.rate(*finer, m_level); - - edge_matcher->match(partition_config, *finer, edge_matching, *coarse_mapping, no_of_coarser_vertices, permutation); - delete edge_matcher; - - if(partition_config.graph_allready_partitioned) { - contracter->contract_partitioned(partition_config, *finer, - *coarser, edge_matching, - *coarse_mapping, no_of_coarser_vertices, - permutation); - } else { - contracter->contract(partition_config, *finer, - *coarser, edge_matching, - *coarse_mapping, no_of_coarser_vertices, + //if graph not small enough + // perform matching two times + // perform coarsening two times + // call rekursive + //else + // initial partitioning + // + //refinement + NodeID no_of_coarser_vertices = G.number_of_nodes(); + NodeID no_of_finer_vertices = G.number_of_nodes(); + int improvement = 0; + + edge_ratings rating(partition_config); + CoarseMapping* coarse_mapping = new CoarseMapping(); + + graph_access* finer = &G; + matching* edge_matcher = NULL; + contraction* contracter = new contraction(); + PartitionConfig copy_of_partition_config = partition_config; + graph_access* coarser = new graph_access(); + + Matching edge_matching; + NodePermutationMap permutation; + + coarsening_configurator coarsening_config; + coarsening_config.configure_coarsening(partition_config, &edge_matcher, m_level); + + rating.rate(*finer, m_level); + + edge_matcher->match(partition_config, *finer, edge_matching, *coarse_mapping, no_of_coarser_vertices, permutation); + delete edge_matcher; + + if(partition_config.graph_allready_partitioned) { + contracter->contract_partitioned(partition_config, *finer, + *coarser, edge_matching, + *coarse_mapping, no_of_coarser_vertices, permutation); - } - - coarser->set_partition_count(partition_config.k); - complete_boundary* coarser_boundary = NULL; - refinement* refine = NULL; - - if(!partition_config.label_propagation_refinement) { - coarser_boundary = new complete_boundary(coarser); - refine = new mixed_refinement(); - } else { - refine = new label_propagation_refinement(); - } - - if(!m_coarsening_stop_rule->stop(no_of_finer_vertices, no_of_coarser_vertices)) { - - PartitionConfig cfg; cfg = partition_config; - - double factor = partition_config.balance_factor; - cfg.upper_bound_partition = (factor +1.0)*partition_config.upper_bound_partition; + } else { + contracter->contract(partition_config, *finer, + *coarser, edge_matching, + *coarse_mapping, no_of_coarser_vertices, + permutation); + } - initial_partitioning init_part; - init_part.perform_initial_partitioning(cfg, *coarser); + coarser->set_partition_count(partition_config.k); + complete_boundary* coarser_boundary = NULL; + refinement* refine = NULL; - if(!partition_config.label_propagation_refinement) coarser_boundary->build(); + if(!partition_config.label_propagation_refinement) { + coarser_boundary = new complete_boundary(coarser); + refine = new mixed_refinement(); + } else { + refine = new label_propagation_refinement(); + } - improvement += refine->perform_refinement(cfg, *coarser, *coarser_boundary); - m_deepest_level = m_level + 1; - } else { - m_level++; + if(!m_coarsening_stop_rule->stop(no_of_finer_vertices, no_of_coarser_vertices)) { - improvement += perform_partitioning_recursive( partition_config, *coarser, &coarser_boundary); - partition_config.graph_allready_partitioned = true; + PartitionConfig cfg; cfg = partition_config; - if(m_level % partition_config.level_split == 0 ) { + double factor = partition_config.balance_factor; + cfg.upper_bound_partition = (factor +1.0)*partition_config.upper_bound_partition; - if(!partition_config.use_fullmultigrid - || m_have_been_level_down.find(m_level) == m_have_been_level_down.end()) { + initial_partitioning init_part; + init_part.perform_initial_partitioning(cfg, *coarser); - if(!partition_config.label_propagation_refinement) { - delete coarser_boundary; + if(!partition_config.label_propagation_refinement) coarser_boundary->build(); - coarser_boundary = new complete_boundary(coarser); - } - m_have_been_level_down[m_level] = true; + improvement += refine->perform_refinement(cfg, *coarser, *coarser_boundary); + m_deepest_level = m_level + 1; + } else { + m_level++; - // configurate the algorithm to use the same amount - // of imbalance as was allowed on this level - PartitionConfig cfg; - cfg = partition_config; - cfg.set_upperbound = false; + improvement += perform_partitioning_recursive( partition_config, *coarser, &coarser_boundary); + partition_config.graph_allready_partitioned = true; - double cur_factor = partition_config.balance_factor/(m_deepest_level-m_level); - cfg.upper_bound_partition = ( (m_level != 0) * cur_factor+1.0)*partition_config.upper_bound_partition; + if(m_level % partition_config.level_split == 0 ) { - // do the next arm of the F-cycle - improvement += perform_partitioning_recursive( cfg, *coarser, &coarser_boundary); - } - } + if(!partition_config.use_fullmultigrid + || m_have_been_level_down.find(m_level) == m_have_been_level_down.end()) { - m_level--; - - } - - if(partition_config.use_balance_singletons && !partition_config.label_propagation_refinement) { - coarser_boundary->balance_singletons( partition_config, *coarser ); - } - - //project - graph_access& fRef = *finer; - graph_access& cRef = *coarser; - forall_nodes(fRef, n) { - NodeID coarser_node = (*coarse_mapping)[n]; - PartitionID coarser_partition_id = cRef.getPartitionIndex(coarser_node); - fRef.setPartitionIndex(n, coarser_partition_id); - } endfor - - finer->set_partition_count(coarser->get_partition_count()); - complete_boundary* current_boundary = NULL; if(!partition_config.label_propagation_refinement) { - current_boundary = new complete_boundary(finer); - current_boundary->build_from_coarser(coarser_boundary, no_of_coarser_vertices, coarse_mapping ); - } + delete coarser_boundary; - PartitionConfig cfg; cfg = partition_config; - double cur_factor = partition_config.balance_factor/(m_deepest_level-m_level); - - //only set the upperbound if it is the first time - //we go down the F-cycle - if( partition_config.set_upperbound ) { - cfg.upper_bound_partition = ( (m_level != 0) * cur_factor+1.0)*partition_config.upper_bound_partition; - } else { - cfg.upper_bound_partition = partition_config.upper_bound_partition; + coarser_boundary = new complete_boundary(coarser); } + m_have_been_level_down[m_level] = true; - improvement += refine->perform_refinement(cfg, *finer, *current_boundary); - - if(c_boundary != NULL) { - delete *c_boundary; - *c_boundary = current_boundary; - } else { - if( current_boundary != NULL ) delete current_boundary; - } + // configurate the algorithm to use the same amount + // of imbalance as was allowed on this level + PartitionConfig cfg; + cfg = partition_config; + cfg.set_upperbound = false; - //std::cout << "finer " << no_of_finer_vertices << std::endl; - delete contracter; - delete coarse_mapping; - delete coarser_boundary; - delete coarser; - delete refine; - - return improvement; + double cur_factor = partition_config.balance_factor/(m_deepest_level-m_level); + cfg.upper_bound_partition = ( (m_level != 0) * cur_factor+1.0)*partition_config.upper_bound_partition; + + // do the next arm of the F-cycle + improvement += perform_partitioning_recursive( cfg, *coarser, &coarser_boundary); + } + } + + m_level--; + + } + + if(partition_config.use_balance_singletons && !partition_config.label_propagation_refinement) { + coarser_boundary->balance_singletons( partition_config, *coarser ); + } + + //project + graph_access& fRef = *finer; + graph_access& cRef = *coarser; + forall_nodes(fRef, n) { + NodeID coarser_node = (*coarse_mapping)[n]; + PartitionID coarser_partition_id = cRef.getPartitionIndex(coarser_node); + fRef.setPartitionIndex(n, coarser_partition_id); + } endfor + + finer->set_partition_count(coarser->get_partition_count()); + complete_boundary* current_boundary = NULL; + if(!partition_config.label_propagation_refinement) { + current_boundary = new complete_boundary(finer); + current_boundary->build_from_coarser(coarser_boundary, no_of_coarser_vertices, coarse_mapping ); + } + + PartitionConfig cfg; cfg = partition_config; + double cur_factor = partition_config.balance_factor/(m_deepest_level-m_level); + + //only set the upperbound if it is the first time + //we go down the F-cycle + if( partition_config.set_upperbound ) { + cfg.upper_bound_partition = ( (m_level != 0) * cur_factor+1.0)*partition_config.upper_bound_partition; + } else { + cfg.upper_bound_partition = partition_config.upper_bound_partition; + } + + improvement += refine->perform_refinement(cfg, *finer, *current_boundary); + + if(c_boundary != NULL) { + delete *c_boundary; + *c_boundary = current_boundary; + } else { + if( current_boundary != NULL ) delete current_boundary; + } + + //std::cout << "finer " << no_of_finer_vertices << std::endl; + delete contracter; + delete coarse_mapping; + delete coarser_boundary; + delete coarser; + delete refine; + + return improvement; } +} \ No newline at end of file diff --git a/parallel/modified_kahip/lib/partition/w_cycles/wcycle_partitioner.h b/parallel/modified_kahip/lib/partition/w_cycles/wcycle_partitioner.h index 2bf78d62..21deef21 100644 --- a/parallel/modified_kahip/lib/partition/w_cycles/wcycle_partitioner.h +++ b/parallel/modified_kahip/lib/partition/w_cycles/wcycle_partitioner.h @@ -13,24 +13,24 @@ #include "data_structure/graph_access.h" #include "partition_config.h" #include "uncoarsening/refinement/refinement.h" - +namespace kahip::modified { class wcycle_partitioner { - public: - wcycle_partitioner( ) : m_level(0) {}; - virtual ~wcycle_partitioner() {}; - int perform_partitioning( const PartitionConfig & config, - graph_access & G); +public: + wcycle_partitioner( ) : m_level(0) {}; + virtual ~wcycle_partitioner() {}; + int perform_partitioning( const PartitionConfig & config, + graph_access & G); - private: - int perform_partitioning_recursive( PartitionConfig & partition_config, - graph_access & G, - complete_boundary ** c_boundary); +private: + int perform_partitioning_recursive( PartitionConfig & partition_config, + graph_access & G, + complete_boundary ** c_boundary); - unsigned m_level; - unsigned m_deepest_level; - stop_rule* m_coarsening_stop_rule; + unsigned m_level; + unsigned m_deepest_level; + stop_rule* m_coarsening_stop_rule; - std::unordered_map m_have_been_level_down; + std::unordered_map m_have_been_level_down; }; - +} #endif /* end of include guard: WCYCLE_PARTITIONER_EPNDQMK */ diff --git a/parallel/modified_kahip/lib/tools/graph_communication.cpp b/parallel/modified_kahip/lib/tools/graph_communication.cpp deleted file mode 100644 index 9ecdff85..00000000 --- a/parallel/modified_kahip/lib/tools/graph_communication.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/****************************************************************************** - * graph_communication.cpp - * * - * Source of KaHIP -- Karlsruhe High Quality Partitioning. - * Christian Schulz - *****************************************************************************/ - -#include - -#include "graph_communication.h" - -graph_communication::graph_communication() { - -} - -graph_communication::~graph_communication() { - -} - -void graph_communication::broadcast_graph( graph_access & G, unsigned root) { - int rank; - MPI_Comm_rank(MPI_COMM_WORLD, &rank); - - //first B-Cast number of nodes and number of edges - unsigned number_of_nodes = 0; - unsigned number_of_edges = 0; - - std::vector< int > buffer(2,0); - if(rank == (int)root) { - buffer[0] = G.number_of_nodes(); - buffer[1] = G.number_of_edges(); - } - MPI_Bcast(&buffer[0], 2, MPI_INT, root, MPI_COMM_WORLD); - - number_of_nodes = buffer[0]; - number_of_edges = buffer[1]; - - int* xadj; - int* adjncy; - int* vwgt; - int* adjwgt; - - if( rank == (int)root) { - xadj = G.UNSAFE_metis_style_xadj_array(); - adjncy = G.UNSAFE_metis_style_adjncy_array(); - - vwgt = G.UNSAFE_metis_style_vwgt_array(); - adjwgt = G.UNSAFE_metis_style_adjwgt_array(); - } else { - xadj = new int[number_of_nodes+1]; - adjncy = new int[number_of_edges]; - - vwgt = new int[number_of_nodes]; - adjwgt = new int[number_of_edges]; - } - - MPI_Bcast(xadj, number_of_nodes+1, MPI_INT, root, MPI_COMM_WORLD); - MPI_Bcast(adjncy, number_of_edges , MPI_INT, root, MPI_COMM_WORLD); - MPI_Bcast(vwgt, number_of_nodes , MPI_INT, root, MPI_COMM_WORLD); - MPI_Bcast(adjwgt, number_of_edges , MPI_INT, root, MPI_COMM_WORLD); - - G.build_from_metis_weighted( number_of_nodes, xadj, adjncy, vwgt, adjwgt); - - delete[] xadj; - delete[] adjncy; - delete[] vwgt; - delete[] adjwgt; - -} diff --git a/parallel/modified_kahip/lib/tools/graph_communication.h b/parallel/modified_kahip/lib/tools/graph_communication.h deleted file mode 100644 index e52d2e6e..00000000 --- a/parallel/modified_kahip/lib/tools/graph_communication.h +++ /dev/null @@ -1,23 +0,0 @@ -/****************************************************************************** - * graph_communication.h - * * - * Source of KaHIP -- Karlsruhe High Quality Partitioning. - * Christian Schulz - *****************************************************************************/ - -#ifndef GRAPH_COMMUNICATION_J5Q2P80G -#define GRAPH_COMMUNICATION_J5Q2P80G - -#include "data_structure/graph_access.h" - -class graph_communication { -public: - graph_communication(); - virtual ~graph_communication(); - - void broadcast_graph( graph_access & G, unsigned root); - -}; - - -#endif /* end of include guard: GRAPH_COMMUNICATION_J5Q2P80G */ diff --git a/parallel/modified_kahip/lib/tools/graph_extractor.cpp b/parallel/modified_kahip/lib/tools/graph_extractor.cpp index e0c7e8ab..1c63eea7 100644 --- a/parallel/modified_kahip/lib/tools/graph_extractor.cpp +++ b/parallel/modified_kahip/lib/tools/graph_extractor.cpp @@ -7,7 +7,7 @@ #include #include "graph_extractor.h" - +namespace kahip::modified { graph_extractor::graph_extractor() { } @@ -21,174 +21,174 @@ void graph_extractor::extract_block(graph_access & G, PartitionID block, std::vector & mapping) { - // build reverse mapping - std::vector reverse_mapping; - NodeID nodes = 0; - NodeID dummy_node = G.number_of_nodes() + 1; - forall_nodes(G, node) { - if(G.getPartitionIndex(node) == block) { - reverse_mapping.push_back(nodes++); - } else { - reverse_mapping.push_back(dummy_node); - } - } endfor - - extracted_block.start_construction(nodes, G.number_of_edges()); - - forall_nodes(G, node) { - if(G.getPartitionIndex(node) == block) { - NodeID new_node = extracted_block.new_node(); - mapping.push_back(node); - extracted_block.setNodeWeight( new_node, G.getNodeWeight(node)); - - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if( G.getPartitionIndex( target ) == block ) { - EdgeID new_edge = extracted_block.new_edge(new_node, reverse_mapping[target]); - extracted_block.setEdgeWeight(new_edge, G.getEdgeWeight(e)); - } - } endfor - } - } endfor - - extracted_block.finish_construction(); + // build reverse mapping + std::vector reverse_mapping; + NodeID nodes = 0; + NodeID dummy_node = G.number_of_nodes() + 1; + forall_nodes(G, node) { + if(G.getPartitionIndex(node) == block) { + reverse_mapping.push_back(nodes++); + } else { + reverse_mapping.push_back(dummy_node); + } + } endfor + + extracted_block.start_construction(nodes, G.number_of_edges()); + + forall_nodes(G, node) { + if(G.getPartitionIndex(node) == block) { + NodeID new_node = extracted_block.new_node(); + mapping.push_back(node); + extracted_block.setNodeWeight( new_node, G.getNodeWeight(node)); + + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + if( G.getPartitionIndex( target ) == block ) { + EdgeID new_edge = extracted_block.new_edge(new_node, reverse_mapping[target]); + extracted_block.setEdgeWeight(new_edge, G.getEdgeWeight(e)); + } + } endfor } + } endfor + extracted_block.finish_construction(); +} -void graph_extractor::extract_two_blocks(graph_access & G, - graph_access & extracted_block_lhs, - graph_access & extracted_block_rhs, + +void graph_extractor::extract_two_blocks(graph_access & G, + graph_access & extracted_block_lhs, + graph_access & extracted_block_rhs, std::vector & mapping_lhs, std::vector & mapping_rhs, NodeWeight & partition_weight_lhs, NodeWeight & partition_weight_rhs) { - PartitionID lhs = 0; - PartitionID rhs = 1; - - // build reverse mapping - std::vector reverse_mapping_lhs; - std::vector reverse_mapping_rhs; - NodeID nodes_lhs = 0; - NodeID nodes_rhs = 0; - partition_weight_lhs = 0; - partition_weight_rhs = 0; - NodeID dummy_node = G.number_of_nodes() + 1; - - forall_nodes(G, node) { - if(G.getPartitionIndex(node) == lhs) { - reverse_mapping_lhs.push_back(nodes_lhs++); - reverse_mapping_rhs.push_back(dummy_node); - partition_weight_lhs += G.getNodeWeight(node); - } else { - reverse_mapping_rhs.push_back(nodes_rhs++); - reverse_mapping_lhs.push_back(dummy_node); - partition_weight_rhs += G.getNodeWeight(node); - } - } endfor - - extracted_block_lhs.start_construction(nodes_lhs, G.number_of_edges()); - extracted_block_rhs.start_construction(nodes_rhs, G.number_of_edges()); - - forall_nodes(G, node) { - if(G.getPartitionIndex(node) == lhs) { - NodeID new_node = extracted_block_lhs.new_node(); - mapping_lhs.push_back(node); - extracted_block_lhs.setNodeWeight(new_node, G.getNodeWeight(node)); - - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if( G.getPartitionIndex( target ) == lhs) { - EdgeID new_edge = extracted_block_lhs.new_edge(new_node, reverse_mapping_lhs[target]); - extracted_block_lhs.setEdgeWeight( new_edge, G.getEdgeWeight(e)); - } - } endfor - - } else { - NodeID new_node = extracted_block_rhs.new_node(); - mapping_rhs.push_back(node); - extracted_block_rhs.setNodeWeight(new_node, G.getNodeWeight(node)); - - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if( G.getPartitionIndex( target ) == rhs) { - EdgeID new_edge = extracted_block_rhs.new_edge(new_node, reverse_mapping_rhs[target]); - extracted_block_rhs.setEdgeWeight( new_edge, G.getEdgeWeight(e)); - } - } endfor - } - } endfor - - extracted_block_lhs.finish_construction(); - extracted_block_rhs.finish_construction(); + PartitionID lhs = 0; + PartitionID rhs = 1; + + // build reverse mapping + std::vector reverse_mapping_lhs; + std::vector reverse_mapping_rhs; + NodeID nodes_lhs = 0; + NodeID nodes_rhs = 0; + partition_weight_lhs = 0; + partition_weight_rhs = 0; + NodeID dummy_node = G.number_of_nodes() + 1; + + forall_nodes(G, node) { + if(G.getPartitionIndex(node) == lhs) { + reverse_mapping_lhs.push_back(nodes_lhs++); + reverse_mapping_rhs.push_back(dummy_node); + partition_weight_lhs += G.getNodeWeight(node); + } else { + reverse_mapping_rhs.push_back(nodes_rhs++); + reverse_mapping_lhs.push_back(dummy_node); + partition_weight_rhs += G.getNodeWeight(node); + } + } endfor + + extracted_block_lhs.start_construction(nodes_lhs, G.number_of_edges()); + extracted_block_rhs.start_construction(nodes_rhs, G.number_of_edges()); + + forall_nodes(G, node) { + if(G.getPartitionIndex(node) == lhs) { + NodeID new_node = extracted_block_lhs.new_node(); + mapping_lhs.push_back(node); + extracted_block_lhs.setNodeWeight(new_node, G.getNodeWeight(node)); + + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + if( G.getPartitionIndex( target ) == lhs) { + EdgeID new_edge = extracted_block_lhs.new_edge(new_node, reverse_mapping_lhs[target]); + extracted_block_lhs.setEdgeWeight( new_edge, G.getEdgeWeight(e)); + } + } endfor + +} else { + NodeID new_node = extracted_block_rhs.new_node(); + mapping_rhs.push_back(node); + extracted_block_rhs.setNodeWeight(new_node, G.getNodeWeight(node)); + + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + if( G.getPartitionIndex( target ) == rhs) { + EdgeID new_edge = extracted_block_rhs.new_edge(new_node, reverse_mapping_rhs[target]); + extracted_block_rhs.setEdgeWeight( new_edge, G.getEdgeWeight(e)); + } + } endfor +} + } endfor + + extracted_block_lhs.finish_construction(); + extracted_block_rhs.finish_construction(); } // Method takes a number of nodes and extracts the underlying subgraph from G // it also assignes block informations -void graph_extractor::extract_two_blocks_connected(graph_access & G, +void graph_extractor::extract_two_blocks_connected(graph_access & G, std::vector lhs_nodes, std::vector rhs_nodes, - PartitionID lhs, + PartitionID lhs, PartitionID rhs, graph_access & pair, std::vector & mapping) { - //// build reverse mapping - std::unordered_map reverse_mapping; - NodeID nodes = 0; - EdgeID edges = 0; // upper bound for number of edges - - for( unsigned i = 0; i < lhs_nodes.size(); i++) { - NodeID node = lhs_nodes[i]; - reverse_mapping[node] = nodes; - edges += G.getNodeDegree(lhs_nodes[i]); - nodes++; - } - for( unsigned i = 0; i < rhs_nodes.size(); i++) { - NodeID node = rhs_nodes[i]; - reverse_mapping[node] = nodes; - edges += G.getNodeDegree(rhs_nodes[i]); - nodes++; - } - - pair.start_construction(nodes, edges); + //// build reverse mapping + std::unordered_map reverse_mapping; + NodeID nodes = 0; + EdgeID edges = 0; // upper bound for number of edges + + for( unsigned i = 0; i < lhs_nodes.size(); i++) { + NodeID node = lhs_nodes[i]; + reverse_mapping[node] = nodes; + edges += G.getNodeDegree(lhs_nodes[i]); + nodes++; + } + for( unsigned i = 0; i < rhs_nodes.size(); i++) { + NodeID node = rhs_nodes[i]; + reverse_mapping[node] = nodes; + edges += G.getNodeDegree(rhs_nodes[i]); + nodes++; + } + + pair.start_construction(nodes, edges); + + for( unsigned i = 0; i < lhs_nodes.size(); i++) { + NodeID node = lhs_nodes[i]; + NodeID new_node = pair.new_node(); + mapping.push_back(node); + + pair.setNodeWeight(new_node, G.getNodeWeight(node)); + pair.setPartitionIndex(new_node, 0); + + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + if( G.getPartitionIndex( target ) == lhs || G.getPartitionIndex( target ) == rhs ) { + EdgeID new_edge = pair.new_edge(new_node, reverse_mapping[target]); + pair.setEdgeWeight(new_edge, G.getEdgeWeight(e)); + } + } endfor - for( unsigned i = 0; i < lhs_nodes.size(); i++) { - NodeID node = lhs_nodes[i]; - NodeID new_node = pair.new_node(); - mapping.push_back(node); - - pair.setNodeWeight(new_node, G.getNodeWeight(node)); - pair.setPartitionIndex(new_node, 0); - - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if( G.getPartitionIndex( target ) == lhs || G.getPartitionIndex( target ) == rhs ) { - EdgeID new_edge = pair.new_edge(new_node, reverse_mapping[target]); - pair.setEdgeWeight(new_edge, G.getEdgeWeight(e)); - } - } endfor - - } - - for( unsigned i = 0; i < rhs_nodes.size(); i++) { - NodeID node = rhs_nodes[i]; - NodeID new_node = pair.new_node(); - mapping.push_back(node); +} - pair.setNodeWeight(new_node, G.getNodeWeight(node)); - pair.setPartitionIndex(new_node, 1); + for( unsigned i = 0; i < rhs_nodes.size(); i++) { + NodeID node = rhs_nodes[i]; + NodeID new_node = pair.new_node(); + mapping.push_back(node); - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if( G.getPartitionIndex( target ) == lhs || G.getPartitionIndex( target ) == rhs ) { - EdgeID new_edge = pair.new_edge(new_node, reverse_mapping[target]); - pair.setEdgeWeight(new_edge, G.getEdgeWeight(e)); - } - } endfor + pair.setNodeWeight(new_node, G.getNodeWeight(node)); + pair.setPartitionIndex(new_node, 1); - } + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + if( G.getPartitionIndex( target ) == lhs || G.getPartitionIndex( target ) == rhs ) { + EdgeID new_edge = pair.new_edge(new_node, reverse_mapping[target]); + pair.setEdgeWeight(new_edge, G.getEdgeWeight(e)); + } + } endfor - pair.finish_construction(); } + pair.finish_construction(); +} +} diff --git a/parallel/modified_kahip/lib/tools/graph_extractor.h b/parallel/modified_kahip/lib/tools/graph_extractor.h index 6ea3e553..ef2ae02d 100644 --- a/parallel/modified_kahip/lib/tools/graph_extractor.h +++ b/parallel/modified_kahip/lib/tools/graph_extractor.h @@ -10,35 +10,35 @@ #include "data_structure/graph_access.h" #include "definitions.h" - +namespace kahip::modified { class graph_extractor { - public: - graph_extractor(); - virtual ~graph_extractor(); - - void extract_block(graph_access & G, - graph_access & extracted_block, - PartitionID block, - std::vector & mapping); - - void extract_two_blocks(graph_access & G, - graph_access & extracted_block_lhs, - graph_access & extracted_block_rhs, - std::vector & mapping_lhs, - std::vector & mapping_rhs, - NodeWeight & partition_weight_lhs, - NodeWeight & partition_weight_rhs); - - void extract_two_blocks_connected(graph_access & G, - std::vector lhs_nodes, - std::vector rhs_nodes, - PartitionID lhs, - PartitionID rhs, - graph_access & pair, - std::vector & mapping) ; +public: + graph_extractor(); + virtual ~graph_extractor(); + + void extract_block(graph_access & G, + graph_access & extracted_block, + PartitionID block, + std::vector & mapping); + + void extract_two_blocks(graph_access & G, + graph_access & extracted_block_lhs, + graph_access & extracted_block_rhs, + std::vector & mapping_lhs, + std::vector & mapping_rhs, + NodeWeight & partition_weight_lhs, + NodeWeight & partition_weight_rhs); + + void extract_two_blocks_connected(graph_access & G, + std::vector lhs_nodes, + std::vector rhs_nodes, + PartitionID lhs, + PartitionID rhs, + graph_access & pair, + std::vector & mapping) ; }; - +} #endif /* end of include guard: GRAPH_EXTRACTOR_PDUTVIEF */ diff --git a/parallel/modified_kahip/lib/tools/graph_partition_assertions.h b/parallel/modified_kahip/lib/tools/graph_partition_assertions.h index 0a0fcd37..fd22d394 100644 --- a/parallel/modified_kahip/lib/tools/graph_partition_assertions.h +++ b/parallel/modified_kahip/lib/tools/graph_partition_assertions.h @@ -10,31 +10,31 @@ #include "data_structure/graph_access.h" #include "partition_config.h" - +namespace kahip::modified { class graph_partition_assertions { - public: - graph_partition_assertions( ) {}; - virtual ~graph_partition_assertions() {}; +public: + graph_partition_assertions( ) {}; + virtual ~graph_partition_assertions() {}; - static bool assert_graph_has_kway_partition(const PartitionConfig & config, graph_access & G) { - bool* allpartsthere = new bool[config.k]; - for(unsigned int i = 0; i < config.k; i++) { - allpartsthere[i] = false; - } + static bool assert_graph_has_kway_partition(const PartitionConfig & config, graph_access & G) { + bool* allpartsthere = new bool[config.k]; + for(unsigned int i = 0; i < config.k; i++) { + allpartsthere[i] = false; + } - forall_nodes(G, n) { - allpartsthere[G.getPartitionIndex(n)] = true; - } endfor + forall_nodes(G, n) { + allpartsthere[G.getPartitionIndex(n)] = true; + } endfor - for(unsigned int i = 0; i < config.k; i++) { - ASSERT_TRUE(allpartsthere[i]); - } + for(unsigned int i = 0; i < config.k; i++) { + ASSERT_TRUE(allpartsthere[i]); + } - delete[] allpartsthere; - return true; - }; + delete[] allpartsthere; + return true; + }; }; - +} #endif /* end of include guard: GRAPH_PARTITION_ASSERTIONS_609QZZDM */ diff --git a/parallel/modified_kahip/lib/tools/misc.cpp b/parallel/modified_kahip/lib/tools/misc.cpp index bf1f116f..b5d12b00 100644 --- a/parallel/modified_kahip/lib/tools/misc.cpp +++ b/parallel/modified_kahip/lib/tools/misc.cpp @@ -7,7 +7,7 @@ #include "misc.h" #include "quality_metrics.h" - +namespace kahip::modified { misc::misc() { } @@ -17,33 +17,34 @@ misc::~misc() { } void misc::balance_singletons(const PartitionConfig & config, graph_access & G) { - quality_metrics qm; - std::vector< NodeID > singletons; - std::vector< NodeWeight > block_sizes(config.k,0); - - forall_nodes(G, node) { - block_sizes[G.getPartitionIndex(node)] += G.getNodeWeight(node); - - if(G.getNodeDegree(node) == 0) { - singletons.push_back(node); - } - } endfor - - // use buckets? - for( unsigned i = 0; i < singletons.size(); i++) { - NodeWeight min = block_sizes[0]; - PartitionID p = 0; - for( unsigned j = 0; j < config.k; j++) { - if( block_sizes[j] < min ) { - min = block_sizes[j]; - p = j; - } - } - - NodeID node = singletons[i]; - block_sizes[G.getPartitionIndex(node)] -= G.getNodeWeight(node); - block_sizes[p] += G.getNodeWeight(node); - G.setPartitionIndex(node, p); - } - std::cout << "log> balance after assigning singletons " << qm.balance(G) << std::endl; + quality_metrics qm; + std::vector< NodeID > singletons; + std::vector< NodeWeight > block_sizes(config.k,0); + + forall_nodes(G, node) { + block_sizes[G.getPartitionIndex(node)] += G.getNodeWeight(node); + + if(G.getNodeDegree(node) == 0) { + singletons.push_back(node); + } + } endfor + + // use buckets? + for( unsigned i = 0; i < singletons.size(); i++) { + NodeWeight min = block_sizes[0]; + PartitionID p = 0; + for( unsigned j = 0; j < config.k; j++) { + if( block_sizes[j] < min ) { + min = block_sizes[j]; + p = j; + } + } + + NodeID node = singletons[i]; + block_sizes[G.getPartitionIndex(node)] -= G.getNodeWeight(node); + block_sizes[p] += G.getNodeWeight(node); + G.setPartitionIndex(node, p); + } + std::cout << "log> balance after assigning singletons " << qm.balance(G) << std::endl; } +} \ No newline at end of file diff --git a/parallel/modified_kahip/lib/tools/misc.h b/parallel/modified_kahip/lib/tools/misc.h index 07d37131..0867f445 100644 --- a/parallel/modified_kahip/lib/tools/misc.h +++ b/parallel/modified_kahip/lib/tools/misc.h @@ -10,14 +10,14 @@ #include "data_structure/graph_access.h" #include "partition_config.h" - +namespace kahip::modified { class misc { public: - misc(); - virtual ~misc(); + misc(); + virtual ~misc(); - void balance_singletons(const PartitionConfig & config, graph_access & G); + void balance_singletons(const PartitionConfig & config, graph_access & G); }; - +} #endif /* end of include guard: MISC_C6QUUWLI */ diff --git a/parallel/modified_kahip/lib/tools/mpi_tools.cpp b/parallel/modified_kahip/lib/tools/mpi_tools.cpp deleted file mode 100644 index 556d104f..00000000 --- a/parallel/modified_kahip/lib/tools/mpi_tools.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/****************************************************************************** - * mpi_tools.cpp - * * - * Source of KaHIP -- Karlsruhe High Quality Partitioning. - * Christian Schulz - *****************************************************************************/ - -#include -#include - -#include "mpi_tools.h" - -mpi_tools::mpi_tools() { - -} - -mpi_tools::~mpi_tools() { - - -} - -//void mpi_tools::non_active_wait_for_root() { - //int rank, size; - //MPI_Comm_rank( MPI_COMM_WORLD, &rank); - //MPI_Comm_size( MPI_COMM_WORLD, &size); - - //int MASTER = 0; - - //if(rank == MASTER) { - ////wake up call - //bool wakeup = true; - //for( int to = 1; to < size; to++) { - //MPI_Send(&wakeup, 1, MPI_BOOL, to, 0, MPI_COMM_WORLD); - //} - //} else { - ////non-busy waiting: - //bool stop = false; - //do { - //usleep(5000); - //stop = MPI::COMM_WORLD.Iprobe(MASTER,0); - //} while(!stop); - - //bool wakeup = true; - //MPI::COMM_WORLD.Recv(&wakeup, 1, MPI::BOOL, MASTER, 0); - //} -//} - diff --git a/parallel/modified_kahip/lib/tools/mpi_tools.h b/parallel/modified_kahip/lib/tools/mpi_tools.h deleted file mode 100644 index 9ddb9867..00000000 --- a/parallel/modified_kahip/lib/tools/mpi_tools.h +++ /dev/null @@ -1,21 +0,0 @@ -/****************************************************************************** - * mpi_tools.h - * * - * Source of KaHIP -- Karlsruhe High Quality Partitioning. - * Christian Schulz - *****************************************************************************/ - -#ifndef MPI_TOOLS_HMESDXF2 -#define MPI_TOOLS_HMESDXF2 - - -class mpi_tools { -public: - mpi_tools(); - virtual ~mpi_tools(); - - //static void non_active_wait_for_root(); -}; - - -#endif /* end of include guard: MPI_TOOLS_HMESDXF2 */ diff --git a/parallel/modified_kahip/lib/tools/partition_snapshooter.cpp b/parallel/modified_kahip/lib/tools/partition_snapshooter.cpp index 9b3e070b..34a9b559 100644 --- a/parallel/modified_kahip/lib/tools/partition_snapshooter.cpp +++ b/parallel/modified_kahip/lib/tools/partition_snapshooter.cpp @@ -10,73 +10,73 @@ #include "definitions.h" #include "graph_io.h" #include "partition_snapshooter.h" - +namespace kahip::modified { partition_snapshooter* partition_snapshooter::m_instance = NULL; partition_snapshooter::partition_snapshooter() { - m_buffer_size = 500; - m_idx = 0; + m_buffer_size = 500; + m_idx = 0; } partition_snapshooter::~partition_snapshooter() { - flush_buffer(); + flush_buffer(); } partition_snapshooter * partition_snapshooter::getInstance() { - if( m_instance == NULL ) { - m_instance = new partition_snapshooter(); - } - return m_instance; + if( m_instance == NULL ) { + m_instance = new partition_snapshooter(); + } + return m_instance; } void partition_snapshooter::addSnapshot(graph_access & G) { - std::cout << "idx " << m_partition_map_buffer.size() << std::endl; - std::vector* partition_map = new std::vector(); - m_partition_map_buffer.push_back(partition_map); + std::cout << "idx " << m_partition_map_buffer.size() << std::endl; + std::vector* partition_map = new std::vector(); + m_partition_map_buffer.push_back(partition_map); - forall_nodes(G, node) { - partition_map->push_back(G.getPartitionIndex(node)); - } endfor + forall_nodes(G, node) { + partition_map->push_back(G.getPartitionIndex(node)); + } endfor - if( m_partition_map_buffer.size() > m_buffer_size) { - flush_buffer(); - } + if( m_partition_map_buffer.size() > m_buffer_size) { + flush_buffer(); + } } void partition_snapshooter::addSnapshot(graph_access & G, std::vector & ext_partition_map) { - std::vector* partition_map = new std::vector(); - m_partition_map_buffer.push_back(partition_map); + std::vector* partition_map = new std::vector(); + m_partition_map_buffer.push_back(partition_map); - forall_nodes(G, node) { - partition_map->push_back(ext_partition_map[node]); - } endfor + forall_nodes(G, node) { + partition_map->push_back(ext_partition_map[node]); + } endfor - if( m_partition_map_buffer.size() > m_buffer_size) { - flush_buffer(); - } + if( m_partition_map_buffer.size() > m_buffer_size) { + flush_buffer(); + } } void partition_snapshooter::flush_buffer() { - for( unsigned i = 0; i < m_partition_map_buffer.size(); i++) { - std::stringstream snapshot_name; - snapshot_name << "snapshot_" << m_idx; + for( unsigned i = 0; i < m_partition_map_buffer.size(); i++) { + std::stringstream snapshot_name; + snapshot_name << "snapshot_" << m_idx; - graph_io::writeVector(*(m_partition_map_buffer[i]), snapshot_name.str()); + graph_io::writeVector(*(m_partition_map_buffer[i]), snapshot_name.str()); - m_idx++; - } + m_idx++; + } - //flush buffer - for( int i = m_partition_map_buffer.size()-1; i >= 0; i--) { - delete m_partition_map_buffer[i]; - m_partition_map_buffer.pop_back(); - } + //flush buffer + for( int i = m_partition_map_buffer.size()-1; i >= 0; i--) { + delete m_partition_map_buffer[i]; + m_partition_map_buffer.pop_back(); + } } void partition_snapshooter::set_buffer_size( unsigned int new_buffer_size ) { - m_buffer_size = new_buffer_size; + m_buffer_size = new_buffer_size; +} } - diff --git a/parallel/modified_kahip/lib/tools/partition_snapshooter.h b/parallel/modified_kahip/lib/tools/partition_snapshooter.h index 5ec796cd..8879898a 100644 --- a/parallel/modified_kahip/lib/tools/partition_snapshooter.h +++ b/parallel/modified_kahip/lib/tools/partition_snapshooter.h @@ -9,30 +9,30 @@ #define PARTITION_SNAPSHOOTER_LGCUMS2I #include "data_structure/graph_access.h" - +namespace kahip::modified { //buffered partition snapshooter (singleton) class partition_snapshooter { - public: - static partition_snapshooter * getInstance(); +public: + static partition_snapshooter * getInstance(); - void addSnapshot(graph_access & G); - void addSnapshot(graph_access & G, std::vector & partition_map); + void addSnapshot(graph_access & G); + void addSnapshot(graph_access & G, std::vector & partition_map); - //flushes buffer to disk - void flush_buffer(); - void set_buffer_size( unsigned int new_buffer_size ); - private: - partition_snapshooter(); - partition_snapshooter(const partition_snapshooter&) {} + //flushes buffer to disk + void flush_buffer(); + void set_buffer_size( unsigned int new_buffer_size ); +private: + partition_snapshooter(); + partition_snapshooter(const partition_snapshooter&) {} - virtual ~partition_snapshooter(); - static partition_snapshooter* m_instance; + virtual ~partition_snapshooter(); + static partition_snapshooter* m_instance; - unsigned int m_buffer_size; - unsigned int m_idx; + unsigned int m_buffer_size; + unsigned int m_idx; - std::vector< std::vector< PartitionID >* > m_partition_map_buffer; + std::vector< std::vector< PartitionID >* > m_partition_map_buffer; }; - +} #endif /* end of include guard: PARTITION_SNAPSHOOTER_LGCUMS2I */ diff --git a/parallel/modified_kahip/lib/tools/quality_metrics.cpp b/parallel/modified_kahip/lib/tools/quality_metrics.cpp index cd2d7234..b2d6c11e 100644 --- a/parallel/modified_kahip/lib/tools/quality_metrics.cpp +++ b/parallel/modified_kahip/lib/tools/quality_metrics.cpp @@ -12,7 +12,7 @@ #include "data_structure/union_find.h" #include - +namespace kahip::modified { quality_metrics::quality_metrics() { } @@ -20,195 +20,195 @@ quality_metrics::~quality_metrics () { } EdgeWeight quality_metrics::edge_cut(graph_access & G) { - EdgeWeight edgeCut = 0; - forall_nodes(G, n) { - PartitionID partitionIDSource = G.getPartitionIndex(n); - forall_out_edges(G, e, n) { - NodeID targetNode = G.getEdgeTarget(e); - PartitionID partitionIDTarget = G.getPartitionIndex(targetNode); - - if (partitionIDSource != partitionIDTarget) { - edgeCut += G.getEdgeWeight(e); - } - } endfor - } endfor - return edgeCut/2; + EdgeWeight edgeCut = 0; + forall_nodes(G, n) { + PartitionID partitionIDSource = G.getPartitionIndex(n); + forall_out_edges(G, e, n) { + NodeID targetNode = G.getEdgeTarget(e); + PartitionID partitionIDTarget = G.getPartitionIndex(targetNode); + + if (partitionIDSource != partitionIDTarget) { + edgeCut += G.getEdgeWeight(e); + } + } endfor +} endfor +return edgeCut/2; } EdgeWeight quality_metrics::edge_cut(graph_access & G, int * partition_map) { - EdgeWeight edgeCut = 0; - forall_nodes(G, n) { - PartitionID partitionIDSource = partition_map[n]; - forall_out_edges(G, e, n) { - NodeID targetNode = G.getEdgeTarget(e); - PartitionID partitionIDTarget = partition_map[targetNode]; - - if (partitionIDSource != partitionIDTarget) { - edgeCut += G.getEdgeWeight(e); - } - } endfor - } endfor - return edgeCut/2; + EdgeWeight edgeCut = 0; + forall_nodes(G, n) { + PartitionID partitionIDSource = partition_map[n]; + forall_out_edges(G, e, n) { + NodeID targetNode = G.getEdgeTarget(e); + PartitionID partitionIDTarget = partition_map[targetNode]; + + if (partitionIDSource != partitionIDTarget) { + edgeCut += G.getEdgeWeight(e); + } + } endfor +} endfor +return edgeCut/2; } EdgeWeight quality_metrics::edge_cut(graph_access & G, PartitionID lhs, PartitionID rhs) { - EdgeWeight edgeCut = 0; - forall_nodes(G, n) { - PartitionID partitionIDSource = G.getPartitionIndex(n); - if(partitionIDSource != lhs) continue; - forall_out_edges(G, e, n) { - NodeID targetNode = G.getEdgeTarget(e); - PartitionID partitionIDTarget = G.getPartitionIndex(targetNode); - - if(partitionIDTarget == rhs) { - edgeCut += G.getEdgeWeight(e); - } - } endfor - } endfor - return edgeCut; + EdgeWeight edgeCut = 0; + forall_nodes(G, n) { + PartitionID partitionIDSource = G.getPartitionIndex(n); + if(partitionIDSource != lhs) continue; + forall_out_edges(G, e, n) { + NodeID targetNode = G.getEdgeTarget(e); + PartitionID partitionIDTarget = G.getPartitionIndex(targetNode); + + if(partitionIDTarget == rhs) { + edgeCut += G.getEdgeWeight(e); + } + } endfor +} endfor +return edgeCut; } EdgeWeight quality_metrics::edge_cut_connected(graph_access & G, int * partition_map) { - EdgeWeight edgeCut = 0; - EdgeWeight sumEW = 0; - forall_nodes(G, n) { - PartitionID partitionIDSource = partition_map[n]; - forall_out_edges(G, e, n) { - NodeID targetNode = G.getEdgeTarget(e); - PartitionID partitionIDTarget = partition_map[targetNode]; - - if (partitionIDSource != partitionIDTarget) { - edgeCut += G.getEdgeWeight(e); - } - sumEW+=G.getEdgeWeight(e); - } endfor - } endfor - union_find uf(G.number_of_nodes()); - forall_nodes(G, node) { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if(partition_map[node] == partition_map[target]) { - uf.Union(node, target); - } - } endfor - } endfor - - std::unordered_map size_right; - forall_nodes(G, node) { - size_right[uf.Find(node)] = 1; - } endfor - - - std::cout << "number of connected comp " << size_right.size() << std::endl; - if( size_right.size() == G.get_partition_count()) { - return edgeCut/2; - } else { - return edgeCut/2+sumEW*size_right.size(); - } + EdgeWeight edgeCut = 0; + EdgeWeight sumEW = 0; + forall_nodes(G, n) { + PartitionID partitionIDSource = partition_map[n]; + forall_out_edges(G, e, n) { + NodeID targetNode = G.getEdgeTarget(e); + PartitionID partitionIDTarget = partition_map[targetNode]; + + if (partitionIDSource != partitionIDTarget) { + edgeCut += G.getEdgeWeight(e); + } + sumEW+=G.getEdgeWeight(e); + } endfor +} endfor +union_find uf(G.number_of_nodes()); + forall_nodes(G, node) { + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + if(partition_map[node] == partition_map[target]) { + uf.Union(node, target); + } + } endfor +} endfor + +std::unordered_map size_right; + forall_nodes(G, node) { + size_right[uf.Find(node)] = 1; + } endfor + + + std::cout << "number of connected comp " << size_right.size() << std::endl; + if( size_right.size() == G.get_partition_count()) { + return edgeCut/2; + } else { + return edgeCut/2+sumEW*size_right.size(); + } } EdgeWeight quality_metrics::max_communication_volume(graph_access & G, int * partition_map) { - std::vector block_volume(G.get_partition_count(),0); - forall_nodes(G, node) { - PartitionID block = partition_map[node]; - std::vector block_incident(G.get_partition_count(), false); - block_incident[block] = true; - - int num_incident_blocks = 0; - - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - PartitionID target_block = partition_map[target]; - if(!block_incident[target_block]) { - block_incident[target_block] = true; - num_incident_blocks++; - } - } endfor - block_volume[block] += num_incident_blocks; + std::vector block_volume(G.get_partition_count(),0); + forall_nodes(G, node) { + PartitionID block = partition_map[node]; + std::vector block_incident(G.get_partition_count(), false); + block_incident[block] = true; + + int num_incident_blocks = 0; + + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + PartitionID target_block = partition_map[target]; + if(!block_incident[target_block]) { + block_incident[target_block] = true; + num_incident_blocks++; + } } endfor + block_volume[block] += num_incident_blocks; + } endfor - EdgeWeight max_comm_volume = *(std::max_element(block_volume.begin(), block_volume.end())); - return max_comm_volume; + EdgeWeight max_comm_volume = *(std::max_element(block_volume.begin(), block_volume.end())); + return max_comm_volume; } EdgeWeight quality_metrics::max_communication_volume(graph_access & G) { - std::vector block_volume(G.get_partition_count(),0); - forall_nodes(G, node) { - PartitionID block = G.getPartitionIndex(node); - std::vector block_incident(G.get_partition_count(), false); - block_incident[block] = true; - int num_incident_blocks = 0; - - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - PartitionID target_block = G.getPartitionIndex(target); - if(!block_incident[target_block]) { - block_incident[target_block] = true; - num_incident_blocks++; - } - } endfor - block_volume[block] += num_incident_blocks; + std::vector block_volume(G.get_partition_count(),0); + forall_nodes(G, node) { + PartitionID block = G.getPartitionIndex(node); + std::vector block_incident(G.get_partition_count(), false); + block_incident[block] = true; + int num_incident_blocks = 0; + + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + PartitionID target_block = G.getPartitionIndex(target); + if(!block_incident[target_block]) { + block_incident[target_block] = true; + num_incident_blocks++; + } } endfor + block_volume[block] += num_incident_blocks; + } endfor - EdgeWeight max_comm_volume = *(std::max_element(block_volume.begin(), block_volume.end())); - return max_comm_volume; + EdgeWeight max_comm_volume = *(std::max_element(block_volume.begin(), block_volume.end())); + return max_comm_volume; } int quality_metrics::boundary_nodes(graph_access& G) { - int no_of_boundary_nodes = 0; - forall_nodes(G, n) { - PartitionID partitionIDSource = G.getPartitionIndex(n); - - forall_out_edges(G, e, n) { - NodeID targetNode = G.getEdgeTarget(e); - PartitionID partitionIDTarget = G.getPartitionIndex(targetNode); - - if (partitionIDSource != partitionIDTarget) { - no_of_boundary_nodes++; - break; - } - } endfor - } endfor - return no_of_boundary_nodes; + int no_of_boundary_nodes = 0; + forall_nodes(G, n) { + PartitionID partitionIDSource = G.getPartitionIndex(n); + + forall_out_edges(G, e, n) { + NodeID targetNode = G.getEdgeTarget(e); + PartitionID partitionIDTarget = G.getPartitionIndex(targetNode); + + if (partitionIDSource != partitionIDTarget) { + no_of_boundary_nodes++; + break; + } + } endfor +} endfor +return no_of_boundary_nodes; } double quality_metrics::balance(graph_access& G) { - std::vector part_weights(G.get_partition_count(), 0); + std::vector part_weights(G.get_partition_count(), 0); - double overallWeight = 0; + double overallWeight = 0; - forall_nodes(G, n) { - PartitionID curPartition = G.getPartitionIndex(n); - part_weights[curPartition] += G.getNodeWeight(n); - overallWeight += G.getNodeWeight(n); - } endfor + forall_nodes(G, n) { + PartitionID curPartition = G.getPartitionIndex(n); + part_weights[curPartition] += G.getNodeWeight(n); + overallWeight += G.getNodeWeight(n); + } endfor - double balance_part_weight = ceil(overallWeight / (double)G.get_partition_count()); - double cur_max = -1; + double balance_part_weight = ceil(overallWeight / (double)G.get_partition_count()); + double cur_max = -1; - forall_blocks(G, p) { - double cur = part_weights[p]; - if (cur > cur_max) { - cur_max = cur; - } - } endfor + forall_blocks(G, p) { + double cur = part_weights[p]; + if (cur > cur_max) { + cur_max = cur; + } + } endfor - double percentage = cur_max/balance_part_weight; - return percentage; + double percentage = cur_max/balance_part_weight; + return percentage; } EdgeWeight quality_metrics::objective(const PartitionConfig & config, graph_access & G, int* partition_map) { - if(config.mh_optimize_communication_volume) { - return max_communication_volume(G, partition_map); - } else if(config.mh_penalty_for_unconnected) { - return edge_cut_connected(G, partition_map); - } else { - return edge_cut(G, partition_map); - } + if(config.mh_optimize_communication_volume) { + return max_communication_volume(G, partition_map); + } else if(config.mh_penalty_for_unconnected) { + return edge_cut_connected(G, partition_map); + } else { + return edge_cut(G, partition_map); + } +} } - diff --git a/parallel/modified_kahip/lib/tools/quality_metrics.h b/parallel/modified_kahip/lib/tools/quality_metrics.h index 428b419c..7f29860f 100644 --- a/parallel/modified_kahip/lib/tools/quality_metrics.h +++ b/parallel/modified_kahip/lib/tools/quality_metrics.h @@ -11,22 +11,22 @@ #include "data_structure/graph_access.h" #include "data_structure/matrix/matrix.h" #include "partition_config.h" - +namespace kahip::modified { class quality_metrics { public: - quality_metrics(); - virtual ~quality_metrics (); + quality_metrics(); + virtual ~quality_metrics (); - EdgeWeight edge_cut(graph_access & G); - EdgeWeight edge_cut(graph_access & G, int * partition_map); - EdgeWeight edge_cut(graph_access & G, PartitionID lhs, PartitionID rhs); - EdgeWeight max_communication_volume(graph_access & G); - EdgeWeight max_communication_volume(graph_access & G, int * partition_map); - EdgeWeight objective(const PartitionConfig & config, graph_access & G, int * partition_map); - EdgeWeight edge_cut_connected(graph_access & G, int * partition_map); - int boundary_nodes(graph_access & G); - double balance(graph_access & G); + EdgeWeight edge_cut(graph_access & G); + EdgeWeight edge_cut(graph_access & G, int * partition_map); + EdgeWeight edge_cut(graph_access & G, PartitionID lhs, PartitionID rhs); + EdgeWeight max_communication_volume(graph_access & G); + EdgeWeight max_communication_volume(graph_access & G, int * partition_map); + EdgeWeight objective(const PartitionConfig & config, graph_access & G, int * partition_map); + EdgeWeight edge_cut_connected(graph_access & G, int * partition_map); + int boundary_nodes(graph_access & G); + double balance(graph_access & G); }; - +} #endif /* end of include guard: QUALITY_METRICS_10HC2I5M */ diff --git a/parallel/modified_kahip/lib/tools/random_functions.cpp b/parallel/modified_kahip/lib/tools/random_functions.cpp deleted file mode 100644 index 46c909bf..00000000 --- a/parallel/modified_kahip/lib/tools/random_functions.cpp +++ /dev/null @@ -1,17 +0,0 @@ -/****************************************************************************** - * random_functions.cpp - * * - * Source of KaHIP -- Karlsruhe High Quality Partitioning. - * Christian Schulz - *****************************************************************************/ - -#include "random_functions.h" - -MersenneTwister random_functions::m_mt; -int random_functions::m_seed = 0; - -random_functions::random_functions() { -} - -random_functions::~random_functions() { -} diff --git a/parallel/modified_kahip/lib/tools/random_functions.h b/parallel/modified_kahip/lib/tools/random_functions.h index 542a869c..58cbd3cc 100644 --- a/parallel/modified_kahip/lib/tools/random_functions.h +++ b/parallel/modified_kahip/lib/tools/random_functions.h @@ -12,154 +12,154 @@ #include #include +#include "../../../shared/random_state.h" #include "definitions.h" #include "partition_config.h" -typedef std::mt19937 MersenneTwister; +namespace kahip::modified { +using MersenneTwister = kahip::random_compat::engine_type; class random_functions { - public: - random_functions(); - virtual ~random_functions(); - - template - static void circular_permutation(std::vector & vec) { - if(vec.size() < 2) return; - for( unsigned int i = 0; i < vec.size(); i++) { - vec[i] = i; - } - - unsigned int size = vec.size(); - std::uniform_int_distribution A(0,size-1); - std::uniform_int_distribution B(0,size-1); - - for( unsigned int i = 0; i < size; i++) { - unsigned int posA = A(m_mt); - unsigned int posB = B(m_mt); - - while(posB == posA) { - posB = B(m_mt); - } - - if( posA != vec[posB] && posB != vec[posA]) { - std::swap(vec[posA], vec[posB]); - } - } - - } - - template - static void permutate_vector_fast(std::vector & vec, bool init) { - if(init) { - for( unsigned int i = 0; i < vec.size(); i++) { - vec[i] = i; - } - } - - if(vec.size() < 10) return; - - int distance = 20; - std::uniform_int_distribution A(0, distance); - unsigned int size = vec.size()-4; - for( unsigned int i = 0; i < size; i++) { - unsigned int posA = i; - unsigned int posB = (posA + A(m_mt))%size; - std::swap(vec[posA], vec[posB]); - std::swap(vec[posA+1], vec[posB+1]); - std::swap(vec[posA+2], vec[posB+2]); - std::swap(vec[posA+3], vec[posB+3]); - } - } +public: + + template + static void circular_permutation(std::vector & vec) { + if(vec.size() < 2) return; + for( unsigned int i = 0; i < vec.size(); i++) { + vec[i] = i; + } - template - static void permutate_vector_good(std::vector & vec, bool init) { - if(init) { - for( unsigned int i = 0; i < vec.size(); i++) { - vec[i] = (sometype)i; - } - } - - if(vec.size() < 10) { - permutate_vector_good_small(vec); - return; - } - unsigned int size = vec.size(); - std::uniform_int_distribution A(0,size - 4); - std::uniform_int_distribution B(0,size - 4); - - for( unsigned int i = 0; i < size; i++) { - unsigned int posA = A(m_mt); - unsigned int posB = B(m_mt); - std::swap(vec[posA], vec[posB]); - std::swap(vec[posA+1], vec[posB+1]); - std::swap(vec[posA+2], vec[posB+2]); - std::swap(vec[posA+3], vec[posB+3]); - - } + unsigned int size = vec.size(); + std::uniform_int_distribution A(0,size-1); + std::uniform_int_distribution B(0,size-1); + + for( unsigned int i = 0; i < size; i++) { + unsigned int posA = A(m_mt); + unsigned int posB = B(m_mt); + + while(posB == posA) { + posB = B(m_mt); } - template - static void permutate_vector_good_small(std::vector & vec) { - if(vec.size() < 2) return; - unsigned int size = vec.size(); - std::uniform_int_distribution A(0,size-1); - std::uniform_int_distribution B(0,size-1); - - for( unsigned int i = 0; i < size; i++) { - unsigned int posA = A(m_mt); - unsigned int posB = B(m_mt); - std::swap(vec[posA], vec[posB]); - } + if( posA != vec[posB] && posB != vec[posA]) { + std::swap(vec[posA], vec[posB]); } + } - template - static void permutate_entries(const PartitionConfig & partition_config, - std::vector & vec, - bool init) { - if(init) { - for( unsigned int i = 0; i < vec.size(); i++) { - vec[i] = i; - } - } - - switch(partition_config.permutation_quality) { - case PERMUTATION_QUALITY_NONE: break; - case PERMUTATION_QUALITY_FAST: permutate_vector_fast(vec, false); break; - case PERMUTATION_QUALITY_GOOD: permutate_vector_good(vec, false); break; - } + } + template + static void permutate_vector_fast(std::vector & vec, bool init) { + if(init) { + for( unsigned int i = 0; i < vec.size(); i++) { + vec[i] = i; } - - static bool nextBool() { - std::uniform_int_distribution A(0,1); - return (bool) A(m_mt); } + if(vec.size() < 10) return; + + int distance = 20; + std::uniform_int_distribution A(0, distance); + unsigned int size = vec.size()-4; + for( unsigned int i = 0; i < size; i++) { + unsigned int posA = i; + unsigned int posB = (posA + A(m_mt))%size; + std::swap(vec[posA], vec[posB]); + std::swap(vec[posA+1], vec[posB+1]); + std::swap(vec[posA+2], vec[posB+2]); + std::swap(vec[posA+3], vec[posB+3]); + } + } - //including lb and rb - static unsigned nextInt(unsigned int lb, unsigned int rb) { - std::uniform_int_distribution A(lb,rb); - return A(m_mt); + template + static void permutate_vector_good(std::vector & vec, bool init) { + if(init) { + for( unsigned int i = 0; i < vec.size(); i++) { + vec[i] = (sometype)i; + } } - static double nextDouble(double lb, double rb) { - double rnbr = (double) rand() / (double) RAND_MAX; // rnd in 0,1 - double length = rb - lb; - rnbr *= length; - rnbr += lb; + if(vec.size() < 10) { + permutate_vector_good_small(vec); + return; + } + unsigned int size = vec.size(); + std::uniform_int_distribution A(0,size - 4); + std::uniform_int_distribution B(0,size - 4); + + for( unsigned int i = 0; i < size; i++) { + unsigned int posA = A(m_mt); + unsigned int posB = B(m_mt); + std::swap(vec[posA], vec[posB]); + std::swap(vec[posA+1], vec[posB+1]); + std::swap(vec[posA+2], vec[posB+2]); + std::swap(vec[posA+3], vec[posB+3]); - return rnbr; + } + } + + template + static void permutate_vector_good_small(std::vector & vec) { + if(vec.size() < 2) return; + unsigned int size = vec.size(); + std::uniform_int_distribution A(0,size-1); + std::uniform_int_distribution B(0,size-1); + + for( unsigned int i = 0; i < size; i++) { + unsigned int posA = A(m_mt); + unsigned int posB = B(m_mt); + std::swap(vec[posA], vec[posB]); + } + } + + template + static void permutate_entries(const PartitionConfig & partition_config, + std::vector & vec, + bool init) { + if(init) { + for( unsigned int i = 0; i < vec.size(); i++) { + vec[i] = i; + } } - static void setSeed(int seed) { - m_seed = seed; - srand(seed); - m_mt.seed(m_seed); + switch(partition_config.permutation_quality) { + case PERMUTATION_QUALITY_NONE: break; + case PERMUTATION_QUALITY_FAST: permutate_vector_fast(vec, false); break; + case PERMUTATION_QUALITY_GOOD: permutate_vector_good(vec, false); break; } - private: - static int m_seed; - static MersenneTwister m_mt; -}; + } + + static bool nextBool() { + std::uniform_int_distribution A(0,1); + return static_cast(A(m_mt)); + } + + //including lb and rb + static unsigned nextInt(unsigned int lb, unsigned int rb) { + std::uniform_int_distribution A(lb,rb); + return A(m_mt); + } + + static double nextDouble(double lb, double rb) { + double rnbr = static_cast(rand()) / static_cast(RAND_MAX); + double length = rb - lb; + rnbr *= length; + rnbr += lb; + + return rnbr; + } + + static void setSeed(int seed) { + m_seed = seed; + srand(seed); + m_mt.seed(m_seed); + } + +private: + inline static int& m_seed = kahip::random_compat::seed; + inline static MersenneTwister& m_mt = kahip::random_compat::engine; +}; +} #endif /* end of include guard: RANDOM_FUNCTIONS_RMEPKWYT */ diff --git a/parallel/modified_kahip/lib/tools/timer.h b/parallel/modified_kahip/lib/tools/timer.h index c5abe388..34c5dbcf 100644 --- a/parallel/modified_kahip/lib/tools/timer.h +++ b/parallel/modified_kahip/lib/tools/timer.h @@ -11,31 +11,31 @@ #include #include #include - +namespace kahip::modified { class timer { - public: - timer() { - m_start = timestamp(); - } - - void restart() { - m_start = timestamp(); - } - - double elapsed() { - return timestamp()-m_start; - } - - private: - - /** Returns a timestamp ('now') in seconds (incl. a fractional part). */ - inline double timestamp() { - struct timeval tp; - gettimeofday(&tp, NULL); - return double(tp.tv_sec) + tp.tv_usec / 1000000.; - } - - double m_start; -}; - +public: + timer() { + m_start = timestamp(); + } + + void restart() { + m_start = timestamp(); + } + + double elapsed() { + return timestamp()-m_start; + } + +private: + + /** Returns a timestamp ('now') in seconds (incl. a fractional part). */ + inline double timestamp() { + struct timeval tp; + gettimeofday(&tp, NULL); + return double(tp.tv_sec) + tp.tv_usec / 1000000.; + } + + double m_start; +}; +} #endif /* end of include guard: TIMER_9KPDEP */ diff --git a/parallel/parallel_src/CMakeLists.txt b/parallel/parallel_src/CMakeLists.txt index ae973e7a..2dfec9d1 100644 --- a/parallel/parallel_src/CMakeLists.txt +++ b/parallel/parallel_src/CMakeLists.txt @@ -1,132 +1,605 @@ if(NOT OPTIMIZED_OUTPUT) - add_definitions("-DNOOUTPUT") + target_compile_definitions(kahip_options INTERFACE NOOUTPUT) endif() if(DETERMINISTIC_PARHIP) - add_definitions("-DDETERMINISTIC_PARHIP") + target_compile_definitions(kahip_options INTERFACE DETERMINISTIC_PARHIP) endif() -include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/app) -include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/lib) -include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/lib/tools) -include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/lib/partition) -include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/lib/io) -include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement) -include_directories(${MPI_CXX_INCLUDE_PATH}) -link_libraries(OpenMP::OpenMP_CXX MPI::MPI_CXX) +include(CheckCXXSourceCompiles) +include(CMakePushCheckState) +cmake_push_check_state(RESET) +set(CMAKE_REQUIRED_LIBRARIES MPI::MPI_CXX) +check_cxx_source_compiles( + [=[ + #include + int main() { + MPI_Count counts[1]{}; + MPI_Aint displacements[1]{}; + return MPI_Alltoallv_c( + nullptr, counts, displacements, MPI_BYTE, + nullptr, counts, displacements, MPI_BYTE, MPI_COMM_WORLD + ); + } + ]=] + KAHIP_HAVE_MPI_ALLTOALLV_C +) +check_cxx_source_compiles( + [=[ + #include + int main() { + unsigned long long local{}; + unsigned long long global{}; + return MPI_Allreduce_c( + &local, &global, MPI_Count{1}, MPI_UNSIGNED_LONG_LONG, + MPI_SUM, MPI_COMM_WORLD + ); + } + ]=] + KAHIP_HAVE_MPI_ALLREDUCE_C +) +check_cxx_source_compiles( + [=[ + #include + int main() { + unsigned long long local{}; + unsigned long long global{}; + return MPI_Reduce_c( + &local, &global, MPI_Count{1}, MPI_UNSIGNED_LONG_LONG, + MPI_SUM, 0, MPI_COMM_WORLD + ); + } + ]=] + KAHIP_HAVE_MPI_REDUCE_C +) +check_cxx_source_compiles( + [=[ + #include + int main() { + MPI_Count counts[1]{}; + MPI_Aint displacements[1]{}; + return MPI_Neighbor_alltoallv_c( + nullptr, counts, displacements, MPI_BYTE, + nullptr, counts, displacements, MPI_BYTE, MPI_COMM_WORLD + ); + } + ]=] + KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C +) +check_cxx_source_compiles( + [=[ + #include + int main() { + int counts[1]{}; + int displacements[1]{}; + MPI_Request request = MPI_REQUEST_NULL; + return MPI_Ineighbor_alltoallv( + nullptr, counts, displacements, MPI_BYTE, + nullptr, counts, displacements, MPI_BYTE, MPI_COMM_WORLD, + &request + ); + } + ]=] + KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV +) +if(NOT KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV) + message( + FATAL_ERROR + "KaHIP requires the MPI 3.1 MPI_Ineighbor_alltoallv binding" + ) +endif() +check_cxx_source_compiles( + [=[ + #include + int main() { + MPI_Count counts[1]{}; + MPI_Aint displacements[1]{}; + MPI_Request request = MPI_REQUEST_NULL; + return MPI_Ineighbor_alltoallv_c( + nullptr, counts, displacements, MPI_BYTE, + nullptr, counts, displacements, MPI_BYTE, MPI_COMM_WORLD, + &request + ); + } + ]=] + KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV_C +) +check_cxx_source_compiles( + [=[ + #include + int main() { + int counts[1]{}; + int displacements[1]{}; + MPI_Request request = MPI_REQUEST_NULL; + return MPI_Neighbor_alltoallv_init( + nullptr, counts, displacements, MPI_BYTE, + nullptr, counts, displacements, MPI_BYTE, MPI_COMM_WORLD, + MPI_INFO_NULL, &request + ); + } + ]=] + KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT +) +check_cxx_source_compiles( + [=[ + #include + int main() { + MPI_Count counts[1]{}; + MPI_Aint displacements[1]{}; + MPI_Request request = MPI_REQUEST_NULL; + return MPI_Neighbor_alltoallv_init_c( + nullptr, counts, displacements, MPI_BYTE, + nullptr, counts, displacements, MPI_BYTE, MPI_COMM_WORLD, + MPI_INFO_NULL, &request + ); + } + ]=] + KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT_C +) +cmake_pop_check_state() +set(PARHIP_GENERATED_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated") +file(MAKE_DIRECTORY "${PARHIP_GENERATED_INCLUDE_DIR}") +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/kahip_mpi_capabilities.h.in" + "${PARHIP_GENERATED_INCLUDE_DIR}/kahip_mpi_capabilities.h" +) + +function(kahip_add_parhip_generated_header_set target) + target_sources( + ${target} + PUBLIC + FILE_SET parhip_generated_headers + TYPE HEADERS + BASE_DIRS "${PARHIP_GENERATED_INCLUDE_DIR}" + FILES + "${PARHIP_GENERATED_INCLUDE_DIR}/kahip_mpi_capabilities.h" + ) +endfunction() + +set( + PARHIP_HEADER_BASE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/app + ${CMAKE_CURRENT_SOURCE_DIR}/lib + ${CMAKE_CURRENT_SOURCE_DIR}/lib/tools + ${CMAKE_CURRENT_SOURCE_DIR}/lib/partition + ${CMAKE_CURRENT_SOURCE_DIR}/lib/io + ${CMAKE_CURRENT_SOURCE_DIR}/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement + ${PROJECT_SOURCE_DIR}/parallel/shared +) + +function(kahip_configure_parhip_object target) + kahip_add_header_root_file_sets( + ${target} + parhip_header_root + ${PARHIP_HEADER_BASE_DIRS} + ) + kahip_add_parhip_generated_header_set(${target}) + target_link_libraries( + ${target} + PUBLIC kahip_options MPI::MPI_CXX + PRIVATE kahip_warnings + ) + target_compile_definitions( + ${target} + PRIVATE KAHIP_ENABLE_MPI_TRACE=$ + ) +endfunction() + +file( + GLOB_RECURSE PARHIP_CORE_HEADERS + CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/app/*.h" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/*.h" +) +list( + FILTER PARHIP_CORE_HEADERS + EXCLUDE + REGEX "/lib/dspac/" +) +file( + GLOB PARHIP_MPI_HEADERS + CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/lib/communication/*.h" +) +set( + PARHIP_APPLICATION_HEADERS + "${CMAKE_CURRENT_SOURCE_DIR}/lib/communication/mpi_application.h" +) +list(REMOVE_ITEM PARHIP_MPI_HEADERS ${PARHIP_APPLICATION_HEADERS}) +file( + GLOB_RECURSE PARHIP_GRAPH_HEADERS + CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/lib/data_structure/*.h" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/io/*.h" +) +list( + REMOVE_ITEM PARHIP_CORE_HEADERS + ${PARHIP_MPI_HEADERS} + ${PARHIP_APPLICATION_HEADERS} + ${PARHIP_GRAPH_HEADERS} +) +file( + GLOB_RECURSE PARHIP_DSPAC_HEADERS + CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/lib/dspac/*.h" +) set(LIBPARALLEL_SOURCE_FILES - lib/data_structure/parallel_graph_access.cpp - lib/data_structure/balance_management.cpp - lib/data_structure/balance_management_refinement.cpp - lib/data_structure/balance_management_coarsening.cpp - lib/parallel_label_compress/node_ordering.cpp - lib/parallel_contraction_projection/parallel_contraction.cpp - lib/parallel_contraction_projection/parallel_block_down_propagation.cpp - lib/parallel_contraction_projection/parallel_projection.cpp - lib/distributed_partitioning/distributed_partitioner.cpp - lib/distributed_partitioning/initial_partitioning/initial_partitioning.cpp - lib/distributed_partitioning/initial_partitioning/distributed_evolutionary_partitioning.cpp - lib/distributed_partitioning/initial_partitioning/random_initial_partitioning.cpp - lib/communication/mpi_tools.cpp - lib/communication/dummy_operations.cpp - lib/io/parallel_graph_io.cpp - lib/io/parallel_vector_io.cpp - lib/tools/random_functions.cpp - lib/tools/distributed_quality_metrics.cpp - extern/argtable3-3.2.2/argtable3.c) -add_library(libparallel OBJECT ${LIBPARALLEL_SOURCE_FILES}) -target_include_directories(libparallel PUBLIC $) - -set(LIBGRAPH2BGF_SOURCE_FILES - lib/data_structure/parallel_graph_access.cpp - lib/io/parallel_graph_io.cpp - lib/data_structure/balance_management.cpp - lib/data_structure/balance_management_refinement.cpp - lib/data_structure/balance_management_coarsening.cpp) -add_library(libgraph2bgf OBJECT ${LIBGRAPH2BGF_SOURCE_FILES}) - -set(LIBEDGELIST_SOURCE_FILES - lib/data_structure/parallel_graph_access.cpp - lib/io/parallel_graph_io.cpp - lib/data_structure/balance_management.cpp - lib/data_structure/balance_management_refinement.cpp - lib/data_structure/balance_management_coarsening.cpp - extern/argtable3-3.2.2/argtable3.c) -add_library(libedgelist OBJECT ${LIBEDGELIST_SOURCE_FILES}) + lib/parallel_label_compress/node_ordering.cpp + lib/parallel_contraction_projection/parallel_contraction.cpp + lib/parallel_contraction_projection/parallel_block_down_propagation.cpp + lib/parallel_contraction_projection/parallel_projection.cpp + lib/distributed_partitioning/distributed_partitioner.cpp + lib/distributed_partitioning/initial_partitioning/initial_partitioning.cpp + lib/distributed_partitioning/initial_partitioning/distributed_evolutionary_partitioning.cpp + lib/distributed_partitioning/initial_partitioning/random_initial_partitioning.cpp + lib/communication/mpi_tools.cpp + lib/communication/dummy_operations.cpp + lib/io/parallel_vector_io.cpp + lib/tools/distributed_quality_metrics.cpp +) + +set( + PARHIP_GRAPH_SOURCE_FILES + lib/data_structure/parallel_graph_access.cpp + lib/data_structure/balance_management.cpp + lib/data_structure/balance_management_refinement.cpp + lib/data_structure/balance_management_coarsening.cpp + lib/io/parallel_graph_io.cpp +) +add_library(parhip_graph_obj OBJECT ${PARHIP_GRAPH_SOURCE_FILES}) +kahip_configure_parhip_object(parhip_graph_obj) +kahip_add_private_header_set( + parhip_graph_obj + "${CMAKE_CURRENT_SOURCE_DIR}" + ${PARHIP_GRAPH_HEADERS} +) +set( + PARHIP_MPI_SOURCE_FILES + lib/communication/mpi_adapter.cpp + lib/communication/mpi_neighbors.cpp + lib/communication/mpi_failure.cpp + lib/communication/ghost_exchange_plan.cpp +) +add_library(parhip_mpi_obj OBJECT ${PARHIP_MPI_SOURCE_FILES}) +kahip_configure_parhip_object(parhip_mpi_obj) +kahip_add_private_header_set( + parhip_mpi_obj + "${CMAKE_CURRENT_SOURCE_DIR}" + ${PARHIP_MPI_HEADERS} +) +target_link_libraries( + parhip_mpi_obj + PRIVATE kahip_fatal_diagnostics +) + +add_library( + parhip_mpi_application_obj + OBJECT lib/communication/mpi_application.cpp +) +kahip_configure_parhip_object(parhip_mpi_application_obj) +kahip_add_private_header_set( + parhip_mpi_application_obj + "${CMAKE_CURRENT_SOURCE_DIR}" + ${PARHIP_APPLICATION_HEADERS} +) +target_link_libraries( + parhip_mpi_application_obj + PRIVATE kahip_fatal_diagnostics +) + +add_library(parhip_core_obj OBJECT ${LIBPARALLEL_SOURCE_FILES}) +kahip_configure_parhip_object(parhip_core_obj) +kahip_add_private_header_set( + parhip_core_obj + "${CMAKE_CURRENT_SOURCE_DIR}" + ${PARHIP_CORE_HEADERS} +) +target_link_libraries( + parhip_core_obj + PRIVATE + libmodified_kahip_interface + argtable3 + kahip_version + kahip_fatal_diagnostics +) + +add_library(parallel STATIC) +kahip_add_header_root_file_sets( + parallel + parhip_header_root + ${PARHIP_HEADER_BASE_DIRS} +) +kahip_add_parhip_generated_header_set(parallel) +target_link_libraries( + parallel + PRIVATE + parhip_graph_obj + parhip_core_obj + parhip_mpi_obj + kahip_options + kahip_warnings +) +target_compile_definitions( + parallel + PUBLIC KAHIP_ENABLE_MPI_TRACE=$ +) +target_link_libraries(parallel PUBLIC libmodified_kahip_interface) +target_link_libraries(parallel PUBLIC argtable3) +target_link_libraries(parallel PUBLIC kahip_version) +target_link_libraries(parallel PUBLIC MPI::MPI_CXX) + +add_library(libedgelist STATIC) +target_link_libraries( + libedgelist + PRIVATE + parhip_graph_obj + parhip_mpi_obj + kahip_options + kahip_warnings +) +target_link_libraries(libedgelist PUBLIC MPI::MPI_CXX argtable3) +kahip_add_header_root_file_sets( + libedgelist + parhip_header_root + ${PARHIP_HEADER_BASE_DIRS} +) set(LIBDSPAC_SOURCE_FILES - lib/dspac/dspac.cpp - lib/dspac/edge_balanced_graph_io.cpp) -add_library(libdspac OBJECT ${LIBDSPAC_SOURCE_FILES}) - -add_executable(parhip app/parhip.cpp $) -target_compile_definitions(parhip PRIVATE "-DGRAPH_GENERATOR_MPI" "-DGRAPHGEN_DISTRIBUTED_MEMORY" "-DPARALLEL_LABEL_COMPRESSION") -target_link_libraries(parhip PRIVATE libmodified_kahip_interface) -install(TARGETS parhip DESTINATION bin) - -add_executable(toolbox app/toolbox.cpp $) -target_compile_definitions(toolbox PRIVATE "-DGRAPH_GENERATOR_MPI" "-DGRAPHGEN_DISTRIBUTED_MEMORY" "-DTOOLBOX") -target_link_libraries(toolbox PRIVATE libmodified_kahip_interface) -install(TARGETS toolbox DESTINATION bin) - -add_executable(graph2binary app/graph2binary.cpp $) -target_compile_definitions(graph2binary PRIVATE "-DGRAPH_GENERATOR_MPI" "-DGRAPHGEN_DISTRIBUTED_MEMORY" "-DPARALLEL_LABEL_COMPRESSION" "-DGRAPH2DGF") -install(TARGETS graph2binary DESTINATION bin) - -add_executable(graph2binary_external app/graph2binary_external.cpp $) -target_compile_definitions(graph2binary_external PRIVATE "-DGRAPHGEN_DISTRIBUTED_MEMORY" "-DPARALLEL_LABEL_COMPRESSION" "-DGRAPH2DGF") -install(TARGETS graph2binary_external DESTINATION bin) - -add_executable(readbgf app/readbgf.cpp $) -target_compile_definitions(readbgf PRIVATE "-DGRAPH_GENERATOR_MPI" "-DGRAPHGEN_DISTRIBUTED_MEMORY" "-DPARALLEL_LABEL_COMPRESSION") -install(TARGETS readbgf DESTINATION bin) - -add_executable(edge_list_to_metis_graph app/edge_list_to_metis_graph.cpp $) -target_compile_definitions(edge_list_to_metis_graph PRIVATE "-DGRAPH_GENERATOR_MPI" "-DGRAPHGEN_DISTRIBUTED_MEMORY" "-DKRONECKER_GENERATOR_PROGRAM") -target_link_libraries(edge_list_to_metis_graph PRIVATE libmodified_kahip_interface) -install(TARGETS edge_list_to_metis_graph DESTINATION bin) - -#add_executable(friendster_list_to_metis_graph app/friendster_list_to_metis_graph.cpp $) -#target_compile_definitions(friendster_list_to_metis_graph PRIVATE "-DGRAPH_GENERATOR_MPI" "-DGRAPHGEN_DISTRIBUTED_MEMORY" "-DKRONECKER_GENERATOR_PROGRAM") -#target_link_libraries(edge_list_to_metis_graph PRIVATE libmodified_kahip_interface) -#install(TARGETS friendster_list_to_metis_graph DESTINATION bin) - -add_executable(dspac app/dspac.cpp $ $) -target_compile_definitions(dspac PRIVATE "-DGRAPH_GENERATOR_MPI -DGRAPHGEN_DISTRIBUTED_MEMORY" "-DPARALLEL_LABEL_COMPRESSION") -target_link_libraries(dspac PRIVATE libmodified_kahip_interface) -install(TARGETS dspac DESTINATION bin) - -add_library(parhip_interface SHARED interface/parhip_interface.cpp $) -target_compile_definitions(parhip_interface PRIVATE "-DGRAPH_GENERATOR_MPI" "-DGRAPHGEN_DISTRIBUTED_MEMORY" "-DPARALLEL_LABEL_COMPRESSION") -target_include_directories(parhip_interface PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/interface) -target_link_libraries(parhip_interface PRIVATE libmodified_kahip_interface) -set_target_properties(parhip_interface PROPERTIES PUBLIC_HEADER interface/parhip_interface.h) -install(TARGETS parhip_interface - LIBRARY DESTINATION lib - PUBLIC_HEADER DESTINATION include - ) + lib/dspac/dspac.cpp + lib/dspac/edge_balanced_graph_io.cpp +) +add_library(parhip_dspac_obj OBJECT ${LIBDSPAC_SOURCE_FILES}) +kahip_configure_parhip_object(parhip_dspac_obj) +kahip_add_private_header_set( + parhip_dspac_obj + "${CMAKE_CURRENT_SOURCE_DIR}" + ${PARHIP_DSPAC_HEADERS} +) +add_library(libdspac STATIC) +target_link_libraries( + libdspac + PRIVATE parhip_dspac_obj kahip_options kahip_warnings +) +target_link_libraries(libdspac PUBLIC MPI::MPI_CXX) +kahip_add_header_root_file_sets( + libdspac + parhip_header_root + ${PARHIP_HEADER_BASE_DIRS} +) + +add_executable(parhip app/parhip.cpp) +target_compile_definitions( + parhip + PRIVATE + GRAPH_GENERATOR_MPI + GRAPHGEN_DISTRIBUTED_MEMORY + PARALLEL_LABEL_COMPRESSION +) +target_link_libraries(parhip PRIVATE kahip_options kahip_warnings) +target_link_libraries(parhip PRIVATE parhip_mpi_application_obj parallel) +target_link_libraries(parhip PRIVATE MPI::MPI_CXX) +target_link_libraries(parhip PRIVATE argtable3) +install(TARGETS parhip RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") + +add_executable(toolbox app/toolbox.cpp) +target_compile_definitions( + toolbox + PRIVATE GRAPH_GENERATOR_MPI GRAPHGEN_DISTRIBUTED_MEMORY TOOLBOX +) +target_link_libraries(toolbox PRIVATE parhip_mpi_application_obj parallel) +target_link_libraries(toolbox PRIVATE MPI::MPI_CXX) +target_link_libraries(toolbox PRIVATE kahip_options kahip_warnings) +install(TARGETS toolbox RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") + +add_executable(graph2binary app/graph2binary.cpp) +target_compile_definitions( + graph2binary + PRIVATE + GRAPH_GENERATOR_MPI + GRAPHGEN_DISTRIBUTED_MEMORY + PARALLEL_LABEL_COMPRESSION + GRAPH2DGF +) +target_link_libraries( + graph2binary + PRIVATE parhip_mpi_application_obj parhip_graph_obj parhip_mpi_obj +) +target_link_libraries(graph2binary PRIVATE kahip_version) +target_link_libraries(graph2binary PRIVATE MPI::MPI_CXX) +target_link_libraries(graph2binary PRIVATE kahip_options kahip_warnings) +install(TARGETS graph2binary RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") + +add_executable(graph2binary_external app/graph2binary_external.cpp) +target_compile_definitions( + graph2binary_external + PRIVATE + GRAPHGEN_DISTRIBUTED_MEMORY + PARALLEL_LABEL_COMPRESSION + GRAPH2DGF +) +target_link_libraries( + graph2binary_external + PRIVATE parhip_mpi_application_obj parhip_graph_obj parhip_mpi_obj +) +target_link_libraries(graph2binary_external PRIVATE kahip_version) +target_link_libraries(graph2binary_external PRIVATE kahip_options kahip_warnings) +install( + TARGETS graph2binary_external + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" +) + +add_executable(readbgf app/readbgf.cpp) +target_compile_definitions( + readbgf + PRIVATE + GRAPH_GENERATOR_MPI + GRAPHGEN_DISTRIBUTED_MEMORY + PARALLEL_LABEL_COMPRESSION +) +target_link_libraries( + readbgf + PRIVATE parhip_mpi_application_obj parhip_graph_obj parhip_mpi_obj +) +target_link_libraries(readbgf PRIVATE kahip_version) +target_link_libraries(readbgf PRIVATE kahip_options kahip_warnings) +install(TARGETS readbgf RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") + +add_executable(edge_list_to_metis_graph app/edge_list_to_metis_graph.cpp) +target_compile_definitions( + edge_list_to_metis_graph + PRIVATE + GRAPH_GENERATOR_MPI + GRAPHGEN_DISTRIBUTED_MEMORY + KRONECKER_GENERATOR_PROGRAM +) +target_link_libraries( + edge_list_to_metis_graph + PRIVATE libmodified_kahip_interface +) +target_link_libraries(edge_list_to_metis_graph PRIVATE libedgelist) +target_link_libraries( + edge_list_to_metis_graph + PRIVATE parhip_mpi_application_obj +) +target_link_libraries(edge_list_to_metis_graph PRIVATE kahip_version) +target_link_libraries(edge_list_to_metis_graph PRIVATE kahip_options kahip_warnings) +install( + TARGETS edge_list_to_metis_graph + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" +) + +add_executable(dspac app/dspac.cpp) +target_compile_definitions( + dspac + PRIVATE + GRAPH_GENERATOR_MPI + GRAPHGEN_DISTRIBUTED_MEMORY + PARALLEL_LABEL_COMPRESSION +) +target_link_libraries(dspac PRIVATE parhip_mpi_application_obj parallel) +target_link_libraries(dspac PRIVATE libdspac) +target_link_libraries(dspac PRIVATE kahip_options kahip_warnings) +install(TARGETS dspac RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") + +add_library(parhip_interface SHARED interface/parhip_interface.cpp) +set_target_properties( + parhip_interface + PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON +) +target_sources( + parhip_interface + PUBLIC + FILE_SET public_headers + TYPE HEADERS + BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/interface" + FILES "${CMAKE_CURRENT_SOURCE_DIR}/interface/parhip_interface.h" + PRIVATE + FILE_SET private_headers + TYPE HEADERS + BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/interface" + FILES + "${CMAKE_CURRENT_SOURCE_DIR}/interface/parhip_partition_balance.h" +) +target_compile_definitions( + parhip_interface + PRIVATE + GRAPH_GENERATOR_MPI + GRAPHGEN_DISTRIBUTED_MEMORY + PARALLEL_LABEL_COMPRESSION +) +target_link_libraries( + parhip_interface + PUBLIC MPI::MPI_CXX + PRIVATE parallel +) +target_link_libraries( + parhip_interface PRIVATE kahip_fatal_diagnostics kahip_options kahip_warnings +) +install( + TARGETS parhip_interface + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + FILE_SET public_headers DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" +) # pkg-config -# Collect MPI flags for the .pc file -string(REPLACE ";" " -I" PARHIP_MPI_CFLAGS "-I${MPI_CXX_INCLUDE_DIRS}") -set(PARHIP_MPI_LIBS "") -foreach(_lib ${MPI_CXX_LIBRARIES}) - if(IS_ABSOLUTE "${_lib}") - get_filename_component(_dir "${_lib}" DIRECTORY) - get_filename_component(_name "${_lib}" NAME_WE) - string(REGEX REPLACE "^lib" "" _name "${_name}") - string(APPEND PARHIP_MPI_LIBS " -L${_dir} -l${_name}") - else() - string(APPEND PARHIP_MPI_LIBS " ${_lib}") - endif() -endforeach() -configure_file("${CMAKE_CURRENT_SOURCE_DIR}/parhip_interface.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/parhip_interface.pc" @ONLY) -install(FILES "${CMAKE_CURRENT_BINARY_DIR}/parhip_interface.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") - - -add_library(parhip_interface_static interface/parhip_interface.cpp $) -target_compile_definitions(parhip_interface_static PRIVATE "-DGRAPH_GENERATOR_MPI" "-DGRAPHGEN_DISTRIBUTED_MEMORY" "-DPARALLEL_LABEL_COMPRESSION") -target_include_directories(parhip_interface_static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/interface) -target_link_libraries(parhip_interface_static PRIVATE libmodified_kahip_interface) -install(TARGETS parhip_interface_static DESTINATION lib) +include(KahipPkgConfig) +separate_arguments( + PARHIP_MPI_LINK_OPTIONS + NATIVE_COMMAND + "${MPI_CXX_LINK_FLAGS}" +) +kahip_format_mpi_pkg_config_flags( + PARHIP_MPI_CFLAGS + PARHIP_MPI_LIBS + INCLUDE_DIRECTORIES ${MPI_CXX_INCLUDE_DIRS} + COMPILE_DEFINITIONS ${MPI_CXX_COMPILE_DEFINITIONS} + COMPILE_OPTIONS ${MPI_CXX_COMPILE_OPTIONS} + LINK_OPTIONS ${PARHIP_MPI_LINK_OPTIONS} + LIBRARIES ${MPI_CXX_LIBRARIES} +) +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/parhip_interface.pc.in" + "${CMAKE_CURRENT_BINARY_DIR}/parhip_interface.pc" + @ONLY +) +install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/parhip_interface.pc" + DESTINATION "${KAHIP_INSTALL_PKGCONFIGDIR}" +) + +add_library( + parhip_interface_static + STATIC + interface/parhip_interface.cpp +) +target_sources( + parhip_interface_static + PUBLIC + FILE_SET public_headers + TYPE HEADERS + BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/interface" + FILES "${CMAKE_CURRENT_SOURCE_DIR}/interface/parhip_interface.h" + PRIVATE + FILE_SET private_headers + TYPE HEADERS + BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/interface" + FILES + "${CMAKE_CURRENT_SOURCE_DIR}/interface/parhip_partition_balance.h" +) +target_compile_definitions( + parhip_interface_static + PRIVATE + GRAPH_GENERATOR_MPI + GRAPHGEN_DISTRIBUTED_MEMORY + PARALLEL_LABEL_COMPRESSION +) +target_link_libraries( + parhip_interface_static + PUBLIC MPI::MPI_CXX + PRIVATE + parhip_core_obj + parhip_graph_obj + parhip_mpi_obj + modified_kahip_core_obj + modified_kahip_collective_obj + modified_kahip_evolutionary_interface_obj + parallel +) +target_link_libraries( + parhip_interface_static + PRIVATE kahip_fatal_diagnostics kahip_options kahip_warnings +) +install( + TARGETS parhip_interface_static + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" +) + +if(BUILD_TESTING) + message("Building Tests") + add_subdirectory(tests) +endif() diff --git a/parallel/parallel_src/app/application_math.h b/parallel/parallel_src/app/application_math.h new file mode 100644 index 00000000..3900bea0 --- /dev/null +++ b/parallel/parallel_src/app/application_math.h @@ -0,0 +1,15 @@ +#pragma once + +#include "../../shared/random_state.h" + +namespace parhip::application { +[[nodiscard]] constexpr auto rank_seed(int base_seed, + int process_count, + int rank) noexcept + -> std::optional { + return kahip::random_compat::outer_rank_seed(base_seed, process_count, rank); +} + +using kahip::random_compat::checked_add; +using kahip::random_compat::exact_partition_upper_bound; +} // namespace parhip::application diff --git a/parallel/parallel_src/app/configuration.h b/parallel/parallel_src/app/configuration.h index e2326823..43d07b48 100644 --- a/parallel/parallel_src/app/configuration.h +++ b/parallel/parallel_src/app/configuration.h @@ -9,22 +9,27 @@ #ifndef CONFIGURATION_3APG5V7ZA #define CONFIGURATION_3APG5V7ZA +#include "communication/mpi_handles.h" #include "partition_config.h" - +namespace parhip { class configuration { - public: - configuration() {} ; - virtual ~configuration() {}; +public: + configuration() {} ; + virtual ~configuration() {}; - void standard( PPartitionConfig & config ); - void ultrafast( PPartitionConfig & config ); - void fast( PPartitionConfig & config ); - void eco( PPartitionConfig & config ); - void strong( PPartitionConfig & config ); + void standard( PPartitionConfig & config ); + void ultrafast( PPartitionConfig & config ); + void fast( PPartitionConfig & config ); + void eco( + PPartitionConfig & config, + mpi::communicator_view communicator = + mpi::communicator_view{MPI_COMM_WORLD}); + void strong( PPartitionConfig & config ); }; inline void configuration::ultrafast( PPartitionConfig & partition_config ) { - partition_config.initial_partitioning_algorithm = KAFFPAEULTRAFASTSNW; + partition_config.initial_partitioning_algorithm = + InitialPartitioningAlgorithm::KAFFPAEULTRAFASTSNW; partition_config.no_refinement_in_last_iteration = true; partition_config.stop_factor = 18000; partition_config.num_vcycles = 1; @@ -32,23 +37,28 @@ inline void configuration::ultrafast( PPartitionConfig & partition_config ) { inline void configuration::fast( PPartitionConfig & partition_config ) { - partition_config.initial_partitioning_algorithm = KAFFPAEULTRAFASTSNW; + partition_config.initial_partitioning_algorithm = + InitialPartitioningAlgorithm::KAFFPAEULTRAFASTSNW; partition_config.no_refinement_in_last_iteration = true; partition_config.stop_factor = 18000; } -inline void configuration::eco( PPartitionConfig & partition_config ) { - partition_config.initial_partitioning_algorithm = KAFFPAEFASTSNW; +inline void configuration::eco( + PPartitionConfig & partition_config, + mpi::communicator_view communicator) { + partition_config.initial_partitioning_algorithm = + InitialPartitioningAlgorithm::KAFFPAEFASTSNW; partition_config.no_refinement_in_last_iteration = true; partition_config.stop_factor = 18000; - int size; MPI_Comm_size(MPI_COMM_WORLD, &size); + auto const size = communicator.size(); partition_config.evolutionary_time_limit = 2048/size; partition_config.eco = true; partition_config.num_vcycles = 6; } inline void configuration::strong( PPartitionConfig & partition_config ) { - partition_config.initial_partitioning_algorithm = KAFFPAESTRONGSNW; + partition_config.initial_partitioning_algorithm = + InitialPartitioningAlgorithm::KAFFPAESTRONGSNW; } inline void configuration::standard( PPartitionConfig & partition_config ) { @@ -56,27 +66,28 @@ inline void configuration::standard( PPartitionConfig & partition_config ) { partition_config.k = 2; partition_config.inbalance = 3; partition_config.epsilon = 3; - partition_config.time_limit = 0; - partition_config.evolutionary_time_limit = 0; + partition_config.time_limit = 0; + partition_config.evolutionary_time_limit = 0; partition_config.log_num_verts = 16; partition_config.edge_factor = 16; - partition_config.generate_rgg = false; - partition_config.generate_ba = false; - partition_config.comm_rounds = 128; + partition_config.generate_rgg = false; + partition_config.generate_ba = false; + partition_config.comm_rounds = 128; partition_config.label_iterations = 4; partition_config.label_iterations_coarsening = 3; partition_config.label_iterations_refinement = 6; partition_config.cluster_coarsening_factor = 14; - partition_config.initial_partitioning_algorithm = KAFFPAEFASTSNW; + partition_config.initial_partitioning_algorithm = + InitialPartitioningAlgorithm::KAFFPAEFASTSNW; partition_config.stop_factor = 14000; partition_config.vcycle = false; partition_config.num_vcycles = 2; partition_config.num_tries = 10; - partition_config.node_ordering = DEGREE_NODEORDERING; + partition_config.node_ordering = NodeOrderingType::DEGREE_NODEORDERING; partition_config.no_refinement_in_last_iteration = false; partition_config.ht_fill_factor = 1.6; partition_config.eco = false; - partition_config.binary_io_window_size = 64; + partition_config.binary_io_window_size = 64; partition_config.barabasi_albert_mindegree = 5; partition_config.compute_degree_sequence_ba = true; partition_config.compute_degree_sequence_k_first = false; @@ -84,10 +95,10 @@ inline void configuration::standard( PPartitionConfig & partition_config ) { partition_config.kronecker_internal_only = false; partition_config.generate_ba_32bit = false; partition_config.n = 0; - partition_config.save_partition = false; - partition_config.save_partition_binary = false; + partition_config.save_partition = false; + partition_config.save_partition_binary = false; partition_config.vertex_degree_weights = false; partition_config.converter_evaluate = false; } - +} #endif /* end of include guard: CONFIGURATION_3APG5V7Z */ diff --git a/parallel/parallel_src/app/dspac.cpp b/parallel/parallel_src/app/dspac.cpp index 865da44f..cda89708 100644 --- a/parallel/parallel_src/app/dspac.cpp +++ b/parallel/parallel_src/app/dspac.cpp @@ -7,22 +7,24 @@ *****************************************************************************/ #include + +#include +#include #include -#include -#include -#include -#ifndef _WIN32 -#include -#endif -#include -#include -#include - -#include "communication/mpi_tools.h" +#include +#include +#include +#include +#include + +#include "application_math.h" #include "communication/dummy_operations.h" +#include "communication/mpi_application.h" +#include "communication/mpi_fixed_reduction.h" #include "data_structure/parallel_graph_access.h" #include "distributed_partitioning/distributed_partitioner.h" -#include "io/parallel_graph_io.h" +#include "dspac/dspac.h" +#include "dspac/edge_balanced_graph_io.h" #include "io/parallel_vector_io.h" #include "macros_assertions.h" #include "parse_dspac_parameters.h" @@ -30,223 +32,273 @@ #include "random_functions.h" #include "timer.h" #include "tools/distributed_quality_metrics.h" -#include "dspac/dspac.h" -#include "dspac/edge_balanced_graph_io.h" -static void executeParhip(parallel_graph_access &G, PPartitionConfig &partitionConfig); - -int main(int argn, char **argv) { - MPI_Init(&argn, &argv); +namespace parhip { +namespace { +void require_local_add(NodeWeight& accumulator, + NodeWeight value, + mpi::communicator_view communicator, + std::string_view diagnostic) { + if (!application::checked_add(accumulator, value)) { + mpi::abort_on_capacity_failure(communicator.native_handle(), + "DSPAC executable", diagnostic); + } +} - int rank, size; - MPI_Comm communicator = MPI_COMM_WORLD; - MPI_Comm_rank(communicator, &rank); - MPI_Comm_size(communicator, &size); +[[nodiscard]] auto checked_global_sum(NodeWeight local, + mpi::communicator_view communicator, + std::string_view diagnostic) + -> NodeWeight { + auto const local_values = std::array{local}; + auto global_values = std::array{}; + mpi::all_reduce_checked_sum( + std::span{local_values}, + std::span{global_values}, communicator, + "MPI_Allreduce(DSPAC application checked sum)", "DSPAC executable", + diagnostic); + return global_values.front(); +} - PPartitionConfig partition_config; - DspacConfig dspac_config; - std::string graph_filename; - std::string partition_filename; +[[nodiscard]] auto partition_upper_bound(NodeWeight total_weight, + PPartitionConfig const& config, + mpi::communicator_view communicator) + -> NodeWeight { + auto const result = application::exact_partition_upper_bound( + total_weight, config.k, config.inbalance); + if (!result.has_value()) { + mpi::abort_on_capacity_failure( + communicator.native_handle(), "DSPAC executable", + "partition upper bound exceeds the graph-weight domain"); + } + return *result; +} - int ret_code = parse_dspac_parameters(argn, argv, partition_config, dspac_config, graph_filename, partition_filename); +[[nodiscard]] auto edge_fraction(EdgeWeight edge_count, + EdgeWeight global_edge_count) noexcept + -> double { + return global_edge_count == 0 + ? 0.0 + : static_cast(edge_count) / + static_cast(global_edge_count); +} - if (ret_code) { - MPI_Finalize(); - return 0; +void execute_parhip(parallel_graph_access& graph, + PPartitionConfig& config, + mpi::communicator_view communicator) { + auto const rank = communicator.rank(); + auto const size = communicator.size(); + auto const native_communicator = communicator.native_handle(); + + if (rank == ROOT) { + PRINT(std::cout << "log> cluster coarsening factor is set to " + << config.cluster_coarsening_factor << '\n';) + } + + config.stop_factor /= static_cast(config.k); + auto const seed = application::rank_seed(config.seed, size, rank); + if (!seed.has_value()) { + mpi::abort_on_programming_error(native_communicator, + "invalid rank-specific PRNG seed input"); + } + config.seed = *seed; + std::srand(static_cast(config.seed)); + random_functions::setSeed(config.seed); + + auto const process_count = static_cast(size); + parallel_graph_access::set_comm_rounds(config.comm_rounds / process_count); + parallel_graph_access::set_comm_rounds_up(config.comm_rounds / + process_count); + distributed_partitioner::generate_random_choices(config, communicator); + graph.printMemoryUsage(std::cout); + + auto local_inter_edges = EdgeWeight{0}; + auto local_intra_edges = EdgeWeight{0}; + auto local_weight = NodeWeight{0}; + forall_local_nodes(graph, node) { + require_local_add(local_weight, graph.getNodeWeight(node), communicator, + "local split-graph vertex-weight sum overflow"); + forall_out_edges(graph, edge, node) { + auto const target = graph.getEdgeTarget(edge); + auto& count = graph.is_local_node(target) ? local_intra_edges + : local_inter_edges; + require_local_add(count, EdgeWeight{1}, communicator, + "local split-graph edge-count overflow"); } - if (rank == ROOT) { - std::cout << "graph: " << graph_filename << "\n" - << "infinity edge weight: " << dspac_config.infinity << "\n" - << "seed: " << partition_config.seed << "\n" - << "k: " << partition_config.k << "\n" - << "ncores: " << size << std::endl; + endfor + } + endfor + + auto const local_statistics = + std::array{local_inter_edges, local_intra_edges}; + auto global_statistics = std::array{}; + mpi::all_reduce_checked_sum( + std::span{local_statistics}, + std::span{global_statistics}, communicator, + "MPI_Allreduce(DSPAC application statistics)", "DSPAC executable", + "global split-graph edge count exceeds the KaHIP weight domain"); + auto const [global_inter_edges, global_intra_edges] = global_statistics; + if (rank == ROOT) { + std::cout << "log> ghost edges " + << edge_fraction(global_inter_edges, + graph.number_of_global_edges()) + << '\n'; + std::cout << "log> local edges " + << edge_fraction(global_intra_edges, + graph.number_of_global_edges()) + << '\n'; + } + + if (config.vertex_degree_weights) { + throw std::logic_error{"DSPAC cannot overwrite split-graph vertex weights"}; + } + config.number_of_overall_nodes = graph.number_of_global_nodes(); + auto const global_weight = checked_global_sum( + local_weight, communicator, + "global split-graph vertex-weight sum exceeds the graph-weight domain"); + config.upper_bound_partition = + partition_upper_bound(global_weight, config, communicator); + + auto clock = timer{}; + auto partitioner = distributed_partitioner{}; + partitioner.perform_partitioning(native_communicator, config, graph); + mpi::check_or_abort(MPI_Barrier(native_communicator), native_communicator, + "MPI_Barrier(DSPAC partition completion)"); + + auto const running_time = clock.elapsed(); + auto quality = distributed_quality_metrics{}; + auto const edge_cut = quality.edge_cut(graph, native_communicator); + auto const balance = quality.balance(config, graph, native_communicator); + PRINT(auto const balance_load = + quality.balance_load(config, graph, native_communicator);) + PRINT(auto const balance_load_dist = + quality.balance_load_dist(config, graph, native_communicator);) + + if (rank == ROOT) { + std::cout << "log>=====================================\n"; + std::cout << "log>============AND WE R DONE============\n"; + std::cout << "log>=====================================\n"; + std::cout << "log>total partitioning time elapsed " << running_time << '\n'; + std::cout << "log>final edge cut " << edge_cut << '\n'; + std::cout << "log>final balance " << balance << '\n'; + PRINT(std::cout << "log>final balance load " << balance_load << '\n';) + PRINT(std::cout << "log>final balance load dist " << balance_load_dist + << '\n';) + } + PRINT(quality.comm_vol(config, graph, native_communicator);) + PRINT(quality.comm_vol_dist(graph, native_communicator);) +} +} // namespace +} // namespace parhip + +int main(int argument_count, char** argument_values) { + using namespace parhip; + mpi::application_runtime runtime{argument_count, argument_values, + "DSPAC executable"}; + return runtime.execute([&](mpi::communicator_view communicator) -> int { + auto const rank = communicator.rank(); + auto const size = communicator.size(); + auto const native_communicator = communicator.native_handle(); + + auto partition_config = PPartitionConfig{}; + auto dspac_config = DspacConfig{}; + auto graph_filename = std::string{}; + auto partition_filename = std::string{}; + auto const parse_result = parse_dspac_parameters( + argument_count, argument_values, partition_config, dspac_config, + graph_filename, partition_filename, communicator); + if (parse_result != parse_outcome::continue_execution) { + return parse_result == parse_outcome::early_success ? EXIT_SUCCESS + : EXIT_FAILURE; } - timer t; - MPI_Barrier(MPI_COMM_WORLD); - { - t.restart(); - if (rank == ROOT) std::cout << "running collective dummy operations "; - dummy_operations dop; - dop.run_collective_dummy_operations(); + if (rank == ROOT) { + std::cout << "graph: " << graph_filename << '\n' + << "infinity edge weight: " << dspac_config.infinity << '\n' + << "seed: " << partition_config.seed << '\n' + << "k: " << partition_config.k << '\n' + << "ncores: " << size << '\n'; } - MPI_Barrier(MPI_COMM_WORLD); + auto clock = timer{}; + mpi::check_or_abort(MPI_Barrier(native_communicator), native_communicator, + "MPI_Barrier(before DSPAC warm-up)"); + clock.restart(); if (rank == ROOT) { - std::cout << "took " << t.elapsed() << std::endl; + std::cout << "running collective dummy operations "; + } + auto warm_up = dummy_operations{}; + warm_up.run_collective_dummy_operations(communicator); + mpi::check_or_abort(MPI_Barrier(native_communicator), native_communicator, + "MPI_Barrier(after DSPAC warm-up)"); + if (rank == ROOT) { + std::cout << "took " << clock.elapsed() << '\n'; } - // load input graph - std::vector edge_permutation; - - t.restart(); - parallel_graph_access input_graph(communicator); - edge_balanced_graph_io::read_binary_graph_edge_balanced(input_graph, graph_filename, partition_config, edge_permutation, rank, size); + auto edge_permutation = std::vector{}; + clock.restart(); + auto input_graph = parallel_graph_access{native_communicator}; + edge_balanced_graph_io::read_binary_graph_edge_balanced( + input_graph, graph_filename, partition_config, edge_permutation, + communicator); if (rank == ROOT) { - std::cout << "input IO took " << t.elapsed() << "\n" - << "n(input): " << input_graph.number_of_global_nodes() << "\n" - << "m(input): " << input_graph.number_of_global_edges() << std::endl; + std::cout << "input IO took " << clock.elapsed() << '\n' + << "n(input): " << input_graph.number_of_global_nodes() << '\n' + << "m(input): " << input_graph.number_of_global_edges() << '\n'; } - MPI_Barrier(communicator); + mpi::check_or_abort(MPI_Barrier(native_communicator), native_communicator, + "MPI_Barrier(after DSPAC input)"); - // construct split graph - t.restart(); - parallel_graph_access split_graph(communicator); - dspac splitter(input_graph, communicator, dspac_config.infinity); + clock.restart(); + auto split_graph = parallel_graph_access{native_communicator}; + auto splitter = dspac{input_graph, native_communicator, + dspac_config.infinity}; splitter.construct(split_graph); if (rank == ROOT) { - std::cout << "split graph construction took " << t.elapsed() << "\n" - << "n(split): " << split_graph.number_of_global_nodes() << "\n" - << "m(split): " << split_graph.number_of_global_edges() << std::endl; + std::cout << "split graph construction took " << clock.elapsed() << '\n' + << "n(split): " << split_graph.number_of_global_nodes() << '\n' + << "m(split): " << split_graph.number_of_global_edges() << '\n'; } - // partition split graph - t.restart(); - executeParhip(split_graph, partition_config); + clock.restart(); + execute_parhip(split_graph, partition_config, communicator); if (rank == ROOT) { - std::cout << "parhip took " << t.elapsed() << std::endl; + std::cout << "parhip took " << clock.elapsed() << '\n'; } - // evaluate edge partition - t.restart(); + clock.restart(); splitter.fix_cut_dominant_edges(split_graph); - std::vector edge_partition = splitter.project_partition(split_graph, edge_permutation); - EdgeWeight vertex_cut = splitter.calculate_vertex_cut(partition_config.k, edge_partition); + auto edge_partition = + splitter.project_partition(split_graph, edge_permutation); + auto const vertex_cut = + splitter.calculate_vertex_cut(partition_config.k, edge_partition); if (rank == ROOT) { - std::cout << "evaluation took " << t.elapsed() << "\n" - << "vertex cut: " << vertex_cut << std::endl; - } - - if (partition_config.save_partition || partition_config.save_partition_binary) { - for (NodeID node = 0; node < split_graph.number_of_local_nodes(); ++node) { - split_graph.setNodeLabel(node, edge_partition[node]); - } + std::cout << "evaluation took " << clock.elapsed() << '\n' + << "vertex cut: " << vertex_cut << '\n'; } - if( partition_config.save_partition ) { - parallel_vector_io pvio; - std::string filename = partition_filename.empty() ? "tmpedgepartition.txtp" : partition_filename; - pvio.writePartitionSimpleParallel(split_graph, filename); + if (partition_config.save_partition || + partition_config.save_partition_binary) { + for (NodeID node = 0; node < split_graph.number_of_local_nodes(); ++node) { + split_graph.setNodeLabel(node, edge_partition[node]); + } } - - if( partition_config.save_partition_binary ) { - parallel_vector_io pvio; - std::string filename = partition_filename.empty() ? "tmpedgepartition.binp" : partition_filename; - pvio.writePartitionBinaryParallelPosix(partition_config, split_graph, filename); + if (partition_config.save_partition) { + auto output = parallel_vector_io{}; + auto const filename = partition_filename.empty() + ? std::string{"tmpedgepartition.txtp"} + : partition_filename; + output.writePartitionSimpleParallel(split_graph, filename); } - - MPI_Barrier(MPI_COMM_WORLD); - MPI_Finalize(); -} - -static void executeParhip(parallel_graph_access &G, PPartitionConfig &partitionConfig) { - timer t; - int rank, size; - MPI_Comm communicator = MPI_COMM_WORLD; - MPI_Comm_rank(communicator, &rank); - MPI_Comm_size(communicator, &size); - - MPI_Barrier(MPI_COMM_WORLD); - - if (communicator != MPI_COMM_NULL) { - MPI_Comm_rank(communicator, &rank); - MPI_Comm_size(communicator, &size); - - if (rank == ROOT) { - PRINT(std::cout << "log> cluster coarsening factor is set to " - << partitionConfig.cluster_coarsening_factor << std::endl;) - } - - partitionConfig.stop_factor /= partitionConfig.k; - if (rank != 0) partitionConfig.seed = partitionConfig.seed * size + rank; - srand(static_cast(partitionConfig.seed)); - - random_functions::setSeed(partitionConfig.seed); - parallel_graph_access::set_comm_rounds(partitionConfig.comm_rounds / size); - parallel_graph_access::set_comm_rounds_up(partitionConfig.comm_rounds / size); - distributed_partitioner::generate_random_choices(partitionConfig); - - G.printMemoryUsage(std::cout); - - //compute some stats - EdgeWeight interPEedges = 0; - EdgeWeight localEdges = 0; - forall_local_nodes(G, node) - { - forall_out_edges(G, e, node) - { - NodeID target = G.getEdgeTarget(e); - if (!G.is_local_node(target)) { - interPEedges++; - } else { - localEdges++; - } - } - endfor - } - endfor - - EdgeWeight globalInterEdges = 0; - EdgeWeight globalIntraEdges = 0; - MPI_Reduce(&interPEedges, &globalInterEdges, 1, MPI_UNSIGNED_LONG_LONG, MPI_SUM, ROOT, communicator); - MPI_Reduce(&localEdges, &globalIntraEdges, 1, MPI_UNSIGNED_LONG_LONG, MPI_SUM, ROOT, communicator); - - if (rank == ROOT) { - std::cout << "log> ghost edges " << globalInterEdges / (double) G.number_of_global_edges() << std::endl; - std::cout << "log> local edges " << globalIntraEdges / (double) G.number_of_global_edges() << std::endl; - } - - t.restart(); - double epsilon = (partitionConfig.inbalance) / 100.0; - if (partitionConfig.vertex_degree_weights) { - throw std::logic_error("not allowed to overwrite vertex degrees"); - } else { - partitionConfig.number_of_overall_nodes = G.number_of_global_nodes(); - partitionConfig.upper_bound_partition = - (1 + epsilon) * ceil(G.number_of_global_nodes() / (double) partitionConfig.k); - } - - - distributed_partitioner dpart; - dpart.perform_partitioning(communicator, partitionConfig, G); - - MPI_Barrier(communicator); - - double running_time = t.elapsed(); - distributed_quality_metrics qm; - EdgeWeight edge_cut = qm.edge_cut(G, communicator); - double balance = qm.balance(partitionConfig, G, communicator); - PRINT(double - balance_load = qm.balance_load(partitionConfig, G, communicator);) - PRINT(double - balance_load_dist = qm.balance_load_dist(partitionConfig, G, communicator);) - - if (rank == ROOT) { - std::cout << "log>" << "=====================================" << std::endl; - std::cout << "log>" << "============AND WE R DONE============" << std::endl; - std::cout << "log>" << "=====================================" << std::endl; - std::cout << "log>total partitioning time elapsed " << running_time << std::endl; - std::cout << "log>final edge cut " << edge_cut << std::endl; - std::cout << "log>final balance " << balance << std::endl; - PRINT(std::cout << "log>final balance load " << balance_load << std::endl;) - PRINT(std::cout << "log>final balance load dist " << balance_load_dist << std::endl;) - } - PRINT(qm.comm_vol(partitionConfig, G, communicator);) - PRINT(qm.comm_vol_dist(G, communicator);) + if (partition_config.save_partition_binary) { + auto output = parallel_vector_io{}; + auto const filename = partition_filename.empty() + ? std::string{"tmpedgepartition.binp"} + : partition_filename; + output.writePartitionBinaryParallelPosix(partition_config, split_graph, + filename); } - - MPI_Status st; - int flag; - MPI_Iprobe(MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &flag, &st); - while (flag) { - std::cout << "attention: still incoming messages! rank " << rank << " from " << st.MPI_SOURCE << std::endl; - int message_length; - MPI_Get_count(&st, MPI_UNSIGNED_LONG_LONG, &message_length); - MPI_Status rst; - std::vector message; - message.resize(message_length); - MPI_Recv(&message[0], message_length, MPI_UNSIGNED_LONG_LONG, st.MPI_SOURCE, st.MPI_TAG, MPI_COMM_WORLD, &rst); - MPI_Iprobe(MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &flag, &st); - }; - MPI_Barrier(MPI_COMM_WORLD); + mpi::check_or_abort(MPI_Barrier(native_communicator), native_communicator, + "MPI_Barrier(DSPAC executable completion)"); + return EXIT_SUCCESS; + }); } diff --git a/parallel/parallel_src/app/edge_list_to_metis_graph.cpp b/parallel/parallel_src/app/edge_list_to_metis_graph.cpp index ede1bd1f..2676b6a0 100644 --- a/parallel/parallel_src/app/edge_list_to_metis_graph.cpp +++ b/parallel/parallel_src/app/edge_list_to_metis_graph.cpp @@ -5,119 +5,183 @@ * Christian Schulz *****************************************************************************/ -#include -#include -#include +#include + +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include -#include -#include "partition_config.h" -#include "parse_parameters.h" + +#include "application_math.h" +#include "communication/mpi_application.h" #include "data_structure/hashed_graph.h" #include "data_structure/parallel_graph_access.h" #include "io/parallel_graph_io.h" -using namespace std; - -int main(int argn, char **argv) -{ - - MPI_Init(&argn, &argv); - - PPartitionConfig partition_config; - std::string graph_filename; - - int ret_code = parse_parameters(argn, argv, - partition_config, - graph_filename); - - if(ret_code) { - MPI_Finalize(); - return 0; - } - - - std::ifstream in(graph_filename.c_str()); - if (!in) { - std::cerr << "Error opening " << graph_filename << std::endl; - return 1; - } - - std::string line; - std::getline(in, line); // skip first line - std::cout << line << std::endl; - - std::unordered_map< NodeID, std::unordered_map< NodeID, int> > source_targets; - - std::cout << "starting io" << std::endl; - EdgeID edge_counter = 0; - EdgeID selfloops = 0; - - NodeID source; - NodeID target; - while( !in.eof() ) { - std::getline(in, line); - std::stringstream ss(line); - - ss >> source; - ss >> target; - - if( source == target ) { - std::getline(in, line); - selfloops++; - continue; - } - - if( source_targets[source].find(target) == source_targets[source].end() ) { - source_targets[source][target] = 0; - } - if( source_targets[target].find(source) == source_targets[target].end() ) { - source_targets[target][source] = 0; - } - - source_targets[source][target] += 1; - source_targets[target][source] += 1; - } - - std::cout << "selfloops " << selfloops << std::endl; - std::cout << "io done" << std::endl; - - NodeID distinct_nodes = source_targets.size(); - std::unordered_map< NodeID, NodeID > map_orignal_id_to_consequtive; - //std::unordered_map< NodeID, std::unordered_map< NodeID, bool > >::iterator it; - NodeID counter = 0; - for( auto it = source_targets.begin(); it != source_targets.end(); it++) { - if( map_orignal_id_to_consequtive.find(it->first) == map_orignal_id_to_consequtive.end()) { - map_orignal_id_to_consequtive[it->first] = counter++; - } - edge_counter += it->second.size(); - +namespace { +namespace fs = std::filesystem; + +template +[[nodiscard]] auto parse_number(std::string_view text) -> std::optional { + auto value = Value{}; + auto const result = + std::from_chars(text.data(), text.data() + text.size(), value); + return result.ec == std::errc{} && result.ptr == text.data() + text.size() + ? std::optional{value} + : std::nullopt; +} +} // namespace + +int main(int argument_count, char** argument_values) { + using namespace parhip; + mpi::application_runtime runtime{argument_count, argument_values, + "edge-list converter executable"}; + return runtime.execute([&](mpi::communicator_view communicator) -> int { + auto const rank = communicator.rank(); + if (argument_count != 2) { + if (rank == ROOT) { + std::cout << "usage: edge_list_to_metis inputfilename\n"; + } + return EXIT_FAILURE; + } + if (rank != ROOT) { + return EXIT_SUCCESS; + } + + auto const graph_filename = fs::path{argument_values[1]}; + if (!fs::exists(graph_filename)) { + std::cerr << "Error: File '" << graph_filename.string() + << "' does not exist.\n"; + return EXIT_FAILURE; + } + auto input = std::ifstream{graph_filename}; + if (!input.is_open()) { + std::cerr << "Error: Could not open file '" << graph_filename.string() + << "'.\n"; + return EXIT_FAILURE; + } + + std::cout << "Starting IO...\n"; + auto source_targets = + std::unordered_map>{}; + auto self_loops = EdgeID{0}; + auto line = std::string{}; + while (std::getline(input, line)) { + if (line.empty()) { + continue; + } + auto const line_view = std::string_view{line}; + auto const comma = line_view.find(','); + if (comma == std::string_view::npos) { + std::cerr << "Malformed line (missing comma): '" << line << "'\n"; + continue; + } + + auto const source = parse_number(line_view.substr(0, comma)); + auto const target = parse_number(line_view.substr(comma + 1)); + if (!source.has_value() || !target.has_value()) { + std::cerr << "Error parsing line '" << line + << "': invalid number format.\n"; + continue; + } + if (*source == *target) { + if (!application::checked_add(self_loops, EdgeID{1})) { + mpi::abort_on_capacity_failure( + communicator.native_handle(), "edge-list converter executable", + "self-loop count exceeds the edge domain"); } - std::cout << "starting construction" << std::endl; - - complete_graph_access G; - G.start_construction( distinct_nodes, edge_counter, distinct_nodes, edge_counter); - G.set_range(0, distinct_nodes); - - EdgeID my_count = 0; - for( auto it = source_targets.begin(); it != source_targets.end(); it++) { - NodeID node = G.new_node(); - - for( auto edge_it = source_targets[it->first].begin(); - source_targets[it->first].end() != edge_it; - edge_it++) { - G.new_edge(node, map_orignal_id_to_consequtive[edge_it->first]); - my_count += edge_it->second; - } + continue; + } + auto& forward = source_targets[*source][*target]; + auto& reverse = source_targets[*target][*source]; + if (!application::checked_add(forward, EdgeID{1}) || + !application::checked_add(reverse, EdgeID{1})) { + mpi::abort_on_capacity_failure( + communicator.native_handle(), "edge-list converter executable", + "parallel-edge multiplicity exceeds the edge domain"); + } + } + std::cout << "Self-loops detected: " << self_loops << "\nIO completed.\n"; + + auto node_ids = std::vector{}; + node_ids.reserve(source_targets.size()); + std::ranges::transform(source_targets, std::back_inserter(node_ids), + [](auto const& entry) { return entry.first; }); + std::ranges::sort(node_ids); + + auto node_mapping = std::unordered_map{}; + node_mapping.reserve(node_ids.size()); + for (auto const index : + std::views::iota(std::size_t{0}, node_ids.size())) { + node_mapping.emplace(node_ids[index], static_cast(index)); + } + + auto edge_count = EdgeID{0}; + for (auto const& [node_id, targets] : source_targets) { + static_cast(node_id); + if (!std::in_range(targets.size()) || + !application::checked_add( + edge_count, static_cast(targets.size()))) { + mpi::abort_on_capacity_failure( + communicator.native_handle(), "edge-list converter executable", + "converted edge count exceeds the edge domain"); + } + } + if (!std::in_range(node_ids.size())) { + mpi::abort_on_capacity_failure( + communicator.native_handle(), "edge-list converter executable", + "converted node count exceeds the node domain"); + } + auto const node_count = static_cast(node_ids.size()); + + std::cout << "Starting graph construction...\n"; + auto graph = complete_graph_access{}; + graph.start_construction(node_count, edge_count, node_count, edge_count); + graph.set_range(0, node_count); + auto total_edge_weight = EdgeID{0}; + for (auto const node_id : node_ids) { + auto const new_node = graph.new_node(); + auto targets = std::vector>{}; + targets.reserve(source_targets.at(node_id).size()); + std::ranges::copy(source_targets.at(node_id), std::back_inserter(targets)); + std::ranges::sort(targets, {}, &std::pair::first); + for (auto const& [target_id, multiplicity] : targets) { + graph.new_edge(new_node, node_mapping.at(target_id)); + if (!application::checked_add(total_edge_weight, multiplicity)) { + mpi::abort_on_capacity_failure( + communicator.native_handle(), "edge-list converter executable", + "total edge weight exceeds the edge domain"); } - G.finish_construction(); - std::cout << "my_count " << my_count << std::endl; - std::cout << "my_count/2+selfloops " << (my_count/2+selfloops) << std::endl; - - std::string outputfilename("converted.graph"); - parallel_graph_io::writeGraphSequentially(G, outputfilename); - - - return 0; + } + } + graph.finish_construction(); + + std::cout << "Total edge weight: " << total_edge_weight << '\n'; + std::cout << "Adjusted edge count (accounting for self-loops): " + << total_edge_weight / 2 + self_loops << '\n'; + auto output_filename = graph_filename; + output_filename.replace_extension(".graph"); + auto const write_status = parallel_graph_io::writeGraphSequentially( + graph, output_filename.string()); + if (write_status != 0) { + std::cerr << "Error writing graph to '" << output_filename.string() + << "'.\n"; + return EXIT_FAILURE; + } + std::cout << "Graph successfully written to '" << output_filename.string() + << "'.\n"; + return EXIT_SUCCESS; + }); } diff --git a/parallel/parallel_src/app/friendster_list_to_metis_graph.cpp b/parallel/parallel_src/app/friendster_list_to_metis_graph.cpp index 9b21366d..2170b1ad 100644 --- a/parallel/parallel_src/app/friendster_list_to_metis_graph.cpp +++ b/parallel/parallel_src/app/friendster_list_to_metis_graph.cpp @@ -6,6 +6,7 @@ *****************************************************************************/ #include +#include #include #include #include @@ -14,6 +15,7 @@ #include #include "partition_config.h" #include "parse_parameters.h" +#include "communication/mpi_application.h" #include "data_structure/hashed_graph.h" #include "data_structure/parallel_graph_access.h" #include "io/parallel_graph_io.h" @@ -22,18 +24,24 @@ using namespace std; int main(int argn, char **argv) { - - MPI_Init(&argn, &argv); /* starts MPI */ + using namespace parhip; + mpi::application_runtime runtime{argn, argv, "friendster converter executable"}; + return runtime.execute([&](mpi::communicator_view communicator) -> int { PPartitionConfig partition_config; std::string graph_filename; - int ret_code = parse_parameters(argn, argv, - partition_config, - graph_filename); + auto const ret_code = parse_parameters(argn, argv, + partition_config, + graph_filename, + communicator); - if(ret_code) { - MPI_Finalize(); - return 0; + if(ret_code != parse_outcome::continue_execution) { + return ret_code == parse_outcome::early_success ? EXIT_SUCCESS + : EXIT_FAILURE; + } + + if(communicator.rank() != ROOT) { + return EXIT_SUCCESS; } @@ -129,5 +137,6 @@ int main(int argn, char **argv) parallel_graph_io::writeGraphSequentially(G, outputfilename); - return 0; + return EXIT_SUCCESS; + }); } diff --git a/parallel/parallel_src/app/graph2binary.cpp b/parallel/parallel_src/app/graph2binary.cpp index 988b57d3..2670afbe 100644 --- a/parallel/parallel_src/app/graph2binary.cpp +++ b/parallel/parallel_src/app/graph2binary.cpp @@ -5,52 +5,47 @@ * Christian Schulz *****************************************************************************/ - -#include +#include #include -#include "io/parallel_graph_io.h" - -using namespace std; - -const long fileTypeVersionNumber = 2; -const long header_count = 3; - -int main(int argn, char **argv) -{ - std::cout << "program converts a METIS graph file into a binary (distributed graph format) file. " << std::endl; - - MPI_Init(&argn, &argv); /* starts MPI */ - - int rank, size; - MPI_Comm_rank( MPI_COMM_WORLD, &rank); - MPI_Comm_size( MPI_COMM_WORLD, &size); +#include - if(argn != 3) { - if( rank == ROOT ) { - std::cout << "usage: " ; - std::cout << "graph2binary metisfile outputfilename" << std::endl; - } - MPI_Finalize(); - return 0; - } - - if( size > 1 ) { - std::cout << "currently only one process supported." << std::endl; - MPI_Finalize(); - return 0; - } - - string graph_filename(argv[1]); - string filename(argv[2]); - - std::cout << "Reading graph " << graph_filename << std::endl; - - parallel_graph_access G; - PPartitionConfig config; - parallel_graph_io::readGraphWeighted(config, G, graph_filename, rank, size, MPI_COMM_WORLD); - parallel_graph_io::writeGraphSequentiallyBinary(G, filename); +#include "communication/mpi_application.h" +#include "io/parallel_graph_io.h" - MPI_Finalize(); - return 0; +int main(int argument_count, char** argument_values) { + using namespace parhip; + mpi::application_runtime runtime{argument_count, argument_values, + "graph2binary executable"}; + return runtime.execute([&](mpi::communicator_view communicator) -> int { + auto const rank = communicator.rank(); + auto const size = communicator.size(); + if (rank == ROOT) { + std::cout << "program converts a METIS graph file into a binary " + "(distributed graph format) file.\n"; + } + if (argument_count != 3) { + if (rank == ROOT) { + std::cout << "usage: graph2binary metisfile outputfilename\n"; + } + return EXIT_SUCCESS; + } + if (size != 1) { + if (rank == ROOT) { + std::cout << "currently only one process supported.\n"; + } + return EXIT_SUCCESS; + } + + auto const graph_filename = std::string{argument_values[1]}; + auto const output_filename = std::string{argument_values[2]}; + std::cout << "Reading graph " << graph_filename << '\n'; + + auto graph = parallel_graph_access{communicator.native_handle()}; + auto config = PPartitionConfig{}; + parallel_graph_io::readGraphWeighted( + config, graph, graph_filename, rank, size, + communicator.native_handle()); + parallel_graph_io::writeGraphSequentiallyBinary(graph, output_filename); + return EXIT_SUCCESS; + }); } - diff --git a/parallel/parallel_src/app/graph2binary_external.cpp b/parallel/parallel_src/app/graph2binary_external.cpp index 41330b02..0ef46e66 100644 --- a/parallel/parallel_src/app/graph2binary_external.cpp +++ b/parallel/parallel_src/app/graph2binary_external.cpp @@ -5,47 +5,43 @@ * Christian Schulz *****************************************************************************/ -#include +#include #include -#include "io/parallel_graph_io.h" - -using namespace std; - -const long fileTypeVersionNumber = 2; -const long header_count = 3; - -int main(int argn, char **argv) -{ - std::cout << "program converts a METIS graph file into a binary (distributed graph format) file. " << std::endl; - - MPI_Init(&argn, &argv); /* starts MPI */ +#include - int rank, size; - MPI_Comm_rank( MPI_COMM_WORLD, &rank); - MPI_Comm_size( MPI_COMM_WORLD, &size); - - if(argn != 3) { - if( rank == ROOT ) { - std::cout << "usage: " ; - std::cout << "graph2binary_external metisfile outputfilename" << std::endl; - } - MPI_Finalize(); - return 0; - } - - if( size > 1 ) { - std::cout << "currently only one process supported." << std::endl; - MPI_Finalize(); - return 0; - } - - string graph_filename(argv[1]); - string filename(argv[2]); +#include "communication/mpi_application.h" +#include "io/parallel_graph_io.h" - std::cout << "Reading and writing graph " << graph_filename << std::endl; - parallel_graph_io::writeGraphExternallyBinary(graph_filename, filename); - - MPI_Finalize(); - return 0; +int main(int argument_count, char** argument_values) { + using namespace parhip; + mpi::application_runtime runtime{argument_count, argument_values, + "graph2binary_external executable"}; + return runtime.execute([&](mpi::communicator_view communicator) -> int { + auto const rank = communicator.rank(); + auto const size = communicator.size(); + if (rank == ROOT) { + std::cout << "program converts a METIS graph file into a binary " + "(distributed graph format) file.\n"; + } + if (argument_count != 3) { + if (rank == ROOT) { + std::cout + << "usage: graph2binary_external metisfile outputfilename\n"; + } + return EXIT_SUCCESS; + } + if (size != 1) { + if (rank == ROOT) { + std::cout << "currently only one process supported.\n"; + } + return EXIT_SUCCESS; + } + + auto const graph_filename = std::string{argument_values[1]}; + auto const output_filename = std::string{argument_values[2]}; + std::cout << "Reading and writing graph " << graph_filename << '\n'; + parallel_graph_io::writeGraphExternallyBinary(graph_filename, + output_filename); + return EXIT_SUCCESS; + }); } - diff --git a/parallel/parallel_src/app/parhip.cpp b/parallel/parallel_src/app/parhip.cpp index 124ef0b1..2b690728 100644 --- a/parallel/parallel_src/app/parhip.cpp +++ b/parallel/parallel_src/app/parhip.cpp @@ -6,19 +6,20 @@ *****************************************************************************/ #include + +#include +#include +#include #include -#include -#include -#include -#ifndef _WIN32 -#include -#endif -#include -#include -#include - -#include "communication/mpi_tools.h" +#include +#include +#include + +#include "application_math.h" #include "communication/dummy_operations.h" +#include "communication/mpi_application.h" +#include "communication/mpi_fixed_reduction.h" +#include "communication/mpi_trace.h" #include "data_structure/parallel_graph_access.h" #include "distributed_partitioning/distributed_partitioner.h" #include "io/parallel_graph_io.h" @@ -30,168 +31,265 @@ #include "timer.h" #include "tools/distributed_quality_metrics.h" -int main(int argn, char **argv) { - - MPI_Init(&argn, &argv); /* starts MPI */ - - PPartitionConfig partition_config; - std::string graph_filename; - - int ret_code = parse_parameters(argn, argv, - partition_config, - graph_filename); - - if(ret_code) { - MPI_Finalize(); - return 0; - } +namespace parhip { +namespace { +[[nodiscard]] auto checked_global_sum(NodeWeight local, + mpi::communicator_view communicator, + std::string_view diagnostic) + -> NodeWeight { + auto const local_values = std::array{local}; + auto global_values = std::array{}; + mpi::all_reduce_checked_sum( + std::span{local_values}, + std::span{global_values}, communicator, + "MPI_Allreduce(ParHIP application checked sum)", "parhip executable", + diagnostic); + return global_values.front(); +} - int rank, size; - MPI_Comm communicator = MPI_COMM_WORLD; - MPI_Comm_rank( communicator, &rank); - MPI_Comm_size( communicator, &size); - - timer t; - MPI_Barrier(MPI_COMM_WORLD); - { - t.restart(); - if( rank == ROOT ) std::cout << "running collective dummy operations "; - dummy_operations dop; - dop.run_collective_dummy_operations(); - } - MPI_Barrier(MPI_COMM_WORLD); +[[nodiscard]] auto partition_upper_bound(NodeWeight total_weight, + PPartitionConfig const& config, + mpi::communicator_view communicator) + -> NodeWeight { + auto const result = application::exact_partition_upper_bound( + total_weight, config.k, config.inbalance); + if (!result.has_value()) { + mpi::abort_on_capacity_failure( + communicator.native_handle(), "parhip executable", + "partition upper bound exceeds the graph-weight domain"); + } + return *result; +} - if( rank == ROOT ) { - std::cout << "took " << t.elapsed() << std::endl; - } +void require_local_add(NodeWeight& accumulator, + NodeWeight value, + mpi::communicator_view communicator, + std::string_view diagnostic) { + if (!application::checked_add(accumulator, value)) { + mpi::abort_on_capacity_failure(communicator.native_handle(), + "parhip executable", diagnostic); + } +} - if( communicator != MPI_COMM_NULL) { - MPI_Comm_rank( communicator, &rank); - MPI_Comm_size( communicator, &size); - - if(rank == ROOT) { - PRINT(std::cout << "log> cluster coarsening factor is set to " << partition_config.cluster_coarsening_factor << std::endl;) - } - - partition_config.stop_factor /= partition_config.k; - if(rank != 0) partition_config.seed = partition_config.seed*size+rank; - - srand(partition_config.seed); - - parallel_graph_access G(communicator); - parallel_graph_io::readGraphWeighted(partition_config, G, graph_filename, rank, size, communicator); - //parallel_graph_io::readGraphWeightedFlexible(G, graph_filename, rank, size, communicator); - if( rank == ROOT ) std::cout << "took " << t.elapsed() << std::endl; - if( rank == ROOT ) std::cout << "n:" << G.number_of_global_nodes() << " m: " << G.number_of_global_edges() << std::endl; - - random_functions::setSeed(partition_config.seed); - parallel_graph_access::set_comm_rounds( partition_config.comm_rounds/size ); - parallel_graph_access::set_comm_rounds_up( partition_config.comm_rounds/size); - distributed_partitioner::generate_random_choices( partition_config ); - - G.printMemoryUsage(std::cout); - - //compute some stats - EdgeWeight interPEedges = 0; - EdgeWeight localEdges = 0; - NodeWeight localWeight = 0; - forall_local_nodes(G, node) { - localWeight += G.getNodeWeight(node); - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if(!G.is_local_node(target)) { - interPEedges++; - } else { - localEdges++; - } - } endfor - } endfor - - EdgeWeight globalInterEdges = 0; - EdgeWeight globalIntraEdges = 0; - EdgeWeight globalWeight = 0; - MPI_Reduce(&interPEedges, &globalInterEdges, 1, MPI_UNSIGNED_LONG_LONG, MPI_SUM, ROOT, communicator); - MPI_Reduce(&localEdges, &globalIntraEdges, 1, MPI_UNSIGNED_LONG_LONG, MPI_SUM, ROOT, communicator); - MPI_Allreduce(&localWeight, &globalWeight, 1, MPI_UNSIGNED_LONG_LONG, MPI_SUM, communicator); - - if( rank == ROOT ) { - std::cout << "log> ghost edges " << globalInterEdges/(double)G.number_of_global_edges() << std::endl; - std::cout << "log> local edges " << globalIntraEdges/(double)G.number_of_global_edges() << std::endl; - } - - t.restart(); - double epsilon = (partition_config.inbalance)/100.0; - if( partition_config.vertex_degree_weights ) { - NodeWeight total_load = G.number_of_global_edges()+G.number_of_global_edges(); - partition_config.number_of_overall_nodes = G.number_of_global_nodes(); - partition_config.upper_bound_partition = (1+epsilon)*ceil(total_load/(double)partition_config.k); - - forall_local_nodes(G, node) { - G.setNodeWeight(node, G.getNodeDegree(node)+1); - } endfor - - } else { - partition_config.number_of_overall_nodes = G.number_of_global_nodes(); - partition_config.upper_bound_partition = (1+epsilon)*ceil(globalWeight/(double)partition_config.k); - if( rank == ROOT) { - std::cout << "upper bound on blocks " << partition_config.upper_bound_partition << std::endl; - } - } - - - distributed_partitioner dpart; - dpart.perform_partitioning( communicator, partition_config, G); - - MPI_Barrier(communicator); - - double running_time = t.elapsed(); - distributed_quality_metrics qm; - EdgeWeight edge_cut = qm.edge_cut( G, communicator ); - double balance = qm.balance( partition_config, G, communicator ); - PRINT(double balance_load = qm.balance_load( partition_config, G, communicator );) - PRINT(double balance_load_dist = qm.balance_load_dist( partition_config, G, communicator );) - - if( rank == ROOT ) { - std::cout << "log>" << "=====================================" << std::endl; - std::cout << "log>" << "============AND WE R DONE============" << std::endl; - std::cout << "log>" << "=====================================" << std::endl; - std::cout << "log>total partitioning time elapsed " << running_time << std::endl; - std::cout << "log>final edge cut " << edge_cut << std::endl; - std::cout << "log>final balance " << balance << std::endl; - PRINT(std::cout << "log>final balance load " << balance_load << std::endl;) - PRINT(std::cout << "log>final balance load dist " << balance_load_dist << std::endl;) - } - PRINT(qm.comm_vol( partition_config, G, communicator );) - PRINT(qm.comm_vol_dist( G, communicator );) - - -#ifndef NDEBUG - MPI_Status st; int flag; - MPI_Iprobe(MPI_ANY_SOURCE, MPI_ANY_TAG, communicator, &flag, &st); - while( flag ) { - std::cout << "attention: still incoming messages! rank " << rank << " from " << st.MPI_SOURCE << std::endl; - int message_length; - MPI_Get_count(&st, MPI_UNSIGNED_LONG_LONG, &message_length); - MPI_Status rst; - std::vector message; message.resize(message_length); - MPI_Recv( &message[0], message_length, MPI_UNSIGNED_LONG_LONG, st.MPI_SOURCE, st.MPI_TAG, communicator, &rst); - MPI_Iprobe(MPI_ANY_SOURCE, MPI_ANY_TAG, communicator, &flag, &st); - }; -#endif - - if( partition_config.save_partition ) { - parallel_vector_io pvio; - std::string filename("tmppartition.txtp"); - pvio.writePartitionSimpleParallel(G, filename); - } - - if( partition_config.save_partition_binary ) { - parallel_vector_io pvio; - std::string filename("tmppartition.binp"); - pvio.writePartitionBinaryParallelPosix(partition_config, G, filename); - } +[[nodiscard]] auto edge_fraction(EdgeWeight edge_count, + EdgeWeight global_edge_count) noexcept + -> double { + return global_edge_count == 0 + ? 0.0 + : static_cast(edge_count) / + static_cast(global_edge_count); +} +} // namespace +} // namespace parhip + +int main(int argument_count, char** argument_values) { + using namespace parhip; + mpi::application_runtime runtime{argument_count, argument_values, + "parhip executable"}; + return runtime.execute([&](mpi::communicator_view communicator) -> int { + auto partition_config = PPartitionConfig{}; + auto graph_filename = std::string{}; + auto const parse_result = parse_parameters( + argument_count, argument_values, partition_config, graph_filename, + communicator); + if (parse_result != parse_outcome::continue_execution) { + return parse_result == parse_outcome::early_success ? EXIT_SUCCESS + : EXIT_FAILURE; + } + + auto const rank = communicator.rank(); + auto const size = communicator.size(); + auto const native_communicator = communicator.native_handle(); + auto clock = timer{}; + + mpi::check_or_abort(MPI_Barrier(native_communicator), native_communicator, + "MPI_Barrier(before ParHIP warm-up)"); + clock.restart(); + if (rank == ROOT) { + std::cout << "running collective dummy operations "; + } + auto warm_up = dummy_operations{}; + warm_up.run_collective_dummy_operations(communicator); + mpi::check_or_abort(MPI_Barrier(native_communicator), native_communicator, + "MPI_Barrier(after ParHIP warm-up)"); + if (rank == ROOT) { + std::cout << "took " << clock.elapsed() << '\n'; + } + + if (rank == ROOT) { + PRINT(std::cout << "log> cluster coarsening factor is set to " + << partition_config.cluster_coarsening_factor << '\n';) + } + + partition_config.stop_factor /= static_cast(partition_config.k); + auto const seed = application::rank_seed(partition_config.seed, size, rank); + if (!seed.has_value()) { + mpi::abort_on_programming_error(native_communicator, + "invalid rank-specific PRNG seed input"); + } + partition_config.seed = *seed; + std::srand(static_cast(partition_config.seed)); + + auto graph = parallel_graph_access{native_communicator}; + clock.restart(); + parallel_graph_io::readGraphWeighted(partition_config, graph, + graph_filename, rank, size, + native_communicator); + KAHIP_MPI_TRACE_SET_HIERARCHY(0, 0, mpi::trace::epoch::input); + forall_local_nodes(graph, node) { + KAHIP_MPI_TRACE(mpi::trace::graph_distribution_node( + mpi::trace::current_hierarchy(), graph.getGlobalID(node), rank, + graph.getNodeWeight(node))); + forall_out_edges(graph, edge, node) { + auto const target = graph.getEdgeTarget(edge); + KAHIP_MPI_TRACE(mpi::trace::graph_distribution_edge( + mpi::trace::current_hierarchy(), graph.getGlobalID(node), rank, + graph.getGlobalID(target), graph.getEdgeWeight(edge))); + } + endfor + } + endfor + if (rank == ROOT) { + std::cout << "took " << clock.elapsed() << '\n'; + std::cout << "n:" << graph.number_of_global_nodes() + << " m: " << graph.number_of_global_edges() << '\n'; + } + + random_functions::setSeed(partition_config.seed); + auto const process_count = static_cast(size); + parallel_graph_access::set_comm_rounds(partition_config.comm_rounds / + process_count); + parallel_graph_access::set_comm_rounds_up(partition_config.comm_rounds / + process_count); + distributed_partitioner::generate_random_choices(partition_config, + communicator); + graph.printMemoryUsage(std::cout); + + auto local_inter_edges = EdgeWeight{0}; + auto local_intra_edges = EdgeWeight{0}; + auto local_weight = NodeWeight{0}; + forall_local_nodes(graph, node) { + require_local_add(local_weight, graph.getNodeWeight(node), communicator, + "local graph vertex-weight sum overflow"); + forall_out_edges(graph, edge, node) { + auto const target = graph.getEdgeTarget(edge); + auto& count = graph.is_local_node(target) ? local_intra_edges + : local_inter_edges; + require_local_add(count, EdgeWeight{1}, communicator, + "local graph edge-count overflow"); + } + endfor + } + endfor + + auto const local_statistics = + std::array{local_inter_edges, local_intra_edges, + local_weight}; + auto global_statistics = std::array{}; + mpi::all_reduce_checked_sum( + std::span{local_statistics}, + std::span{global_statistics}, communicator, + "MPI_Allreduce(ParHIP application statistics)", "parhip executable", + "global graph statistic exceeds the KaHIP weight domain"); + auto const [global_inter_edges, global_intra_edges, global_weight] = + global_statistics; + + if (rank == ROOT) { + std::cout << "log> ghost edges " + << edge_fraction(global_inter_edges, + graph.number_of_global_edges()) + << '\n'; + std::cout << "log> local edges " + << edge_fraction(global_intra_edges, + graph.number_of_global_edges()) + << '\n'; + } + + clock.restart(); + partition_config.number_of_overall_nodes = graph.number_of_global_nodes(); + if (partition_config.vertex_degree_weights) { + auto local_total_load = NodeWeight{0}; + forall_local_nodes(graph, node) { + auto const degree = graph.getNodeDegree(node); + if (degree == std::numeric_limits::max()) { + mpi::abort_on_capacity_failure( + native_communicator, "parhip executable", + "degree-plus-one vertex weight exceeds the weight domain"); } - - MPI_Barrier(MPI_COMM_WORLD); - MPI_Finalize(); + auto const weight = static_cast(degree + 1); + graph.setNodeWeight(node, weight); + require_local_add(local_total_load, weight, communicator, + "local degree-weight sum overflow"); + } + endfor + auto const total_load = checked_global_sum( + local_total_load, communicator, + "global degree-weight sum exceeds the graph-weight domain"); + partition_config.upper_bound_partition = + partition_upper_bound(total_load, partition_config, communicator); + } else { + partition_config.upper_bound_partition = + partition_upper_bound(global_weight, partition_config, communicator); + if (rank == ROOT) { + std::cout << "upper bound on blocks " + << partition_config.upper_bound_partition << '\n'; + } + } + + auto partitioner = distributed_partitioner{}; + partitioner.perform_partitioning(native_communicator, partition_config, + graph); + mpi::check_or_abort(MPI_Barrier(native_communicator), native_communicator, + "MPI_Barrier(ParHIP partition completion)"); + + KAHIP_MPI_TRACE_SET_HIERARCHY( + partition_config.num_vcycles == 0 ? 0 + : partition_config.num_vcycles - 1, + 0, mpi::trace::epoch::final_partition); + forall_local_nodes(graph, node) { + KAHIP_MPI_TRACE(mpi::trace::final_partition( + mpi::trace::current_hierarchy(), graph.getGlobalID(node), rank, + graph.getNodeLabel(node))); + } + endfor + mpi::trace::write_rank_file_if_requested(native_communicator); + + auto const running_time = clock.elapsed(); + auto quality = distributed_quality_metrics{}; + auto const edge_cut = quality.edge_cut(graph, native_communicator); + auto const balance = + quality.balance(partition_config, graph, native_communicator); + PRINT(auto const balance_load = + quality.balance_load(partition_config, graph, + native_communicator);) + PRINT(auto const balance_load_dist = + quality.balance_load_dist(partition_config, graph, + native_communicator);) + + if (rank == ROOT) { + std::cout << "log>=====================================\n"; + std::cout << "log>============AND WE R DONE============\n"; + std::cout << "log>=====================================\n"; + std::cout << "log>total partitioning time elapsed " << running_time + << '\n'; + std::cout << "log>final edge cut " << edge_cut << '\n'; + std::cout << "log>final balance " << balance << '\n'; + PRINT(std::cout << "log>final balance load " << balance_load << '\n';) + PRINT(std::cout << "log>final balance load dist " << balance_load_dist + << '\n';) + } + PRINT(quality.comm_vol(partition_config, graph, native_communicator);) + PRINT(quality.comm_vol_dist(graph, native_communicator);) + + if (partition_config.save_partition) { + auto output = parallel_vector_io{}; + output.writePartitionSimpleParallel(graph, "tmppartition.txtp"); + } + if (partition_config.save_partition_binary) { + auto output = parallel_vector_io{}; + output.writePartitionBinaryParallelPosix(partition_config, graph, + "tmppartition.binp"); + } + return EXIT_SUCCESS; + }); } diff --git a/parallel/parallel_src/app/parse_dspac_parameters.h b/parallel/parallel_src/app/parse_dspac_parameters.h index a9976b08..9373c06f 100644 --- a/parallel/parallel_src/app/parse_dspac_parameters.h +++ b/parallel/parallel_src/app/parse_dspac_parameters.h @@ -17,15 +17,18 @@ #endif #endif #include "configuration.h" - +#include "parse_outcome.h" +namespace parhip { struct DspacConfig { EdgeWeight infinity; PartitionID k; }; -int parse_dspac_parameters(int argn, char **argv, PPartitionConfig &partition_config, DspacConfig &dspac_config, - std::string &graph_filename, std::string &out_partition_filename) { +parse_outcome parse_dspac_parameters(int argn, char **argv, PPartitionConfig &partition_config, DspacConfig &dspac_config, + std::string &graph_filename, std::string &out_partition_filename, + mpi::communicator_view communicator) { const char *progname = argv[0]; + auto const rank = communicator.rank(); struct arg_lit *help = arg_lit0(NULL, "help", "Print help."); struct arg_str *filename = arg_str1(NULL, NULL, "FILE", "Path to graph file to partition."); @@ -41,43 +44,45 @@ int parse_dspac_parameters(int argn, char **argv, PPartitionConfig &partition_co // Define argtable. void *argtable[] = { - help, filename, k, seed, infinity, preconfiguration, imbalance, partition_filename, save_partition, save_partition_binary, end - - }; + help, filename, k, seed, infinity, preconfiguration, imbalance, partition_filename, save_partition, save_partition_binary, end + +}; // Parse arguments. int nerrors = arg_parse(argn, argv, argtable); // Catch case that help was requested. if(help->count > 0) { - int rank; - MPI_Comm_rank( MPI_COMM_WORLD, &rank); - if( rank == ROOT ) { printf("Usage: %s", progname); arg_print_syntax(stdout, argtable, "\n"); arg_print_glossary(stdout, argtable," %-40s %s\n"); printf("This is the experimental parallel SPAC program.\n"); - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); } - return 1; + arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); + return parse_outcome::early_success; } if (nerrors > 0) { - int rank; - MPI_Comm_rank( MPI_COMM_WORLD, &rank); if( rank == ROOT ) { arg_print_errors(stderr, end, progname); printf("Try '%s --help' for more information.\n",progname); - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); } - return 1; + arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); + return parse_outcome::invalid_arguments; } configuration cfg; cfg.standard(partition_config); if (k->count > 0) { + if (k->ival[0] <= 0) { + if (rank == ROOT) { + fprintf(stderr, "Number of blocks must be positive.\n"); + } + arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); + return parse_outcome::invalid_arguments; + } partition_config.k = k->ival[0]; dspac_config.k = k->ival[0]; } @@ -99,13 +104,13 @@ int parse_dspac_parameters(int argn, char **argv, PPartitionConfig &partition_co if(preconfiguration->count > 0) { if (strcmp("ecosocial", preconfiguration->sval[0]) == 0) { - cfg.eco(partition_config); + cfg.eco(partition_config, communicator); } else if (strcmp("fastsocial", preconfiguration->sval[0]) == 0) { cfg.fast(partition_config); } else if (strcmp("ultrafastsocial", preconfiguration->sval[0]) == 0) { cfg.ultrafast(partition_config); } else if (strcmp("ecomesh", preconfiguration->sval[0]) == 0) { - cfg.eco(partition_config); + cfg.eco(partition_config, communicator); partition_config.cluster_coarsening_factor = 20000; } else if (strcmp("fastmesh", preconfiguration->sval[0]) == 0) { cfg.fast(partition_config); @@ -114,17 +119,20 @@ int parse_dspac_parameters(int argn, char **argv, PPartitionConfig &partition_co cfg.ultrafast(partition_config); partition_config.cluster_coarsening_factor = 20000; } else { - fprintf(stderr, "Invalid preconfconfiguration variant: \"%s\"\n", preconfiguration->sval[0]); - exit(0); + if (rank == ROOT) { + fprintf(stderr, "Invalid preconfconfiguration variant: \"%s\"\n", preconfiguration->sval[0]); + } + arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); + return parse_outcome::invalid_arguments; } } if(save_partition->count > 0) { - partition_config.save_partition = true; + partition_config.save_partition = true; } if(save_partition_binary->count > 0) { - partition_config.save_partition_binary = true; + partition_config.save_partition_binary = true; } if (imbalance->count > 0) { @@ -133,12 +141,20 @@ int parse_dspac_parameters(int argn, char **argv, PPartitionConfig &partition_co } if (infinity->count > 0) { + if (infinity->ival[0] <= 0) { + if (rank == ROOT) { + fprintf(stderr, "Infinity edge weight must be positive.\n"); + } + arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); + return parse_outcome::invalid_arguments; + } dspac_config.infinity = infinity->ival[0]; } else { dspac_config.infinity = 1000000; } - return 0; + arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); + return parse_outcome::continue_execution; +} } - #endif // KAHIP_PARSE_DSPAC_PARAMETERS_H diff --git a/parallel/parallel_src/app/parse_outcome.h b/parallel/parallel_src/app/parse_outcome.h new file mode 100644 index 00000000..23f5e9c5 --- /dev/null +++ b/parallel/parallel_src/app/parse_outcome.h @@ -0,0 +1,14 @@ +#ifndef KAHIP_PARALLEL_PARSE_OUTCOME_H +#define KAHIP_PARALLEL_PARSE_OUTCOME_H + +namespace parhip { + +enum class parse_outcome { + continue_execution, + early_success, + invalid_arguments, +}; + +} // namespace parhip + +#endif // KAHIP_PARALLEL_PARSE_OUTCOME_H diff --git a/parallel/parallel_src/app/parse_parameters.h b/parallel/parallel_src/app/parse_parameters.h index 08ebc5ce..0c419a89 100644 --- a/parallel/parallel_src/app/parse_parameters.h +++ b/parallel/parallel_src/app/parse_parameters.h @@ -19,13 +19,17 @@ #endif #endif #include "configuration.h" +#include "parse_outcome.h" #include "version.h" - -int parse_parameters(int argn, char **argv, +namespace parhip { +parse_outcome parse_parameters(int argn, char **argv, PPartitionConfig & partition_config, - std::string & graph_filename) { + std::string & graph_filename, + mpi::communicator_view communicator) { const char *progname = argv[0]; + auto const rank = communicator.rank(); + auto const size = communicator.size(); // Setup argtable parameters. struct arg_lit *help = arg_lit0(NULL, "help","Print help."); @@ -65,10 +69,10 @@ int parse_parameters(int argn, char **argv, void* argtable[] = { #ifdef PARALLEL_LABEL_COMPRESSION help, filename, user_seed, version, k, inbalance, preconfiguration, vertex_degree_weights, - save_partition, save_partition_binary, -#elif defined TOOLBOX + save_partition, save_partition_binary, +#elif defined TOOLBOX help, filename, k_opt, input_partition_filename, save_partition, save_partition_binary, converter_evaluate, -#endif +#endif end }; @@ -76,21 +80,15 @@ int parse_parameters(int argn, char **argv, int nerrors = arg_parse(argn, argv, argtable); if (version->count > 0) { - int rank; - MPI_Comm_rank( MPI_COMM_WORLD, &rank); - if( rank == ROOT ) { std::cout << KAHIPVERSION << std::endl; } arg_freetable(argtable_fordeletion, sizeof(argtable_fordeletion) / sizeof(argtable_fordeletion[0])); - return 1; + return parse_outcome::early_success; } // Catch case that help was requested. if(help->count > 0) { - int rank; - MPI_Comm_rank( MPI_COMM_WORLD, &rank); - if( rank == ROOT ) { printf("Usage: %s", progname); arg_print_syntax(stdout, argtable, "\n"); @@ -98,29 +96,41 @@ int parse_parameters(int argn, char **argv, printf("This is the experimental parallel partitioner program.\n"); } arg_freetable(argtable_fordeletion, sizeof(argtable_fordeletion) / sizeof(argtable_fordeletion[0])); - return 1; + return parse_outcome::early_success; } if(nerrors > 0) { - int rank; - MPI_Comm_rank( MPI_COMM_WORLD, &rank); if( rank == ROOT ) { arg_print_errors(stderr, end, progname); printf("Try '%s --help' for more information.\n",progname); } arg_freetable(argtable_fordeletion, sizeof(argtable_fordeletion) / sizeof(argtable_fordeletion[0])); - return 1; + return parse_outcome::invalid_arguments; } configuration cfg; cfg.standard(partition_config); if(k->count > 0) { + if(k->ival[0] <= 0) { + if(rank == ROOT) { + fprintf(stderr, "Number of blocks must be positive.\n"); + } + arg_freetable(argtable_fordeletion, sizeof(argtable_fordeletion) / sizeof(argtable_fordeletion[0])); + return parse_outcome::invalid_arguments; + } partition_config.k = k->ival[0]; } if(k_opt->count > 0) { + if(k_opt->ival[0] <= 0) { + if(rank == ROOT) { + fprintf(stderr, "Number of blocks must be positive.\n"); + } + arg_freetable(argtable_fordeletion, sizeof(argtable_fordeletion) / sizeof(argtable_fordeletion[0])); + return parse_outcome::invalid_arguments; + } partition_config.k = k_opt->ival[0]; } @@ -130,13 +140,15 @@ int parse_parameters(int argn, char **argv, graph_filename = filename->sval[0]; } else { if(partition_config.generate_rgg == false && partition_config.generate_ba == false) { - printf("You must specify a filename or enable the graph generator tag.\n"); + if(rank == ROOT) { + printf("You must specify a filename or enable the graph generator tag.\n"); + } arg_freetable(argtable_fordeletion, sizeof(argtable_fordeletion) / sizeof(argtable_fordeletion[0])); - return 1; + return parse_outcome::invalid_arguments; } } -#else +#else if(filename->count > 0) { graph_filename = filename->sval[0]; } @@ -147,13 +159,13 @@ int parse_parameters(int argn, char **argv, if(preconfiguration->count > 0) { if (strcmp("ecosocial", preconfiguration->sval[0]) == 0) { - cfg.eco(partition_config); + cfg.eco(partition_config, communicator); } else if (strcmp("fastsocial", preconfiguration->sval[0]) == 0) { cfg.fast(partition_config); } else if (strcmp("ultrafastsocial", preconfiguration->sval[0]) == 0) { cfg.ultrafast(partition_config); } else if (strcmp("ecomesh", preconfiguration->sval[0]) == 0) { - cfg.eco(partition_config); + cfg.eco(partition_config, communicator); partition_config.cluster_coarsening_factor = 20000; } else if (strcmp("fastmesh", preconfiguration->sval[0]) == 0) { cfg.fast(partition_config); @@ -162,9 +174,11 @@ int parse_parameters(int argn, char **argv, cfg.ultrafast(partition_config); partition_config.cluster_coarsening_factor = 20000; } else { - fprintf(stderr, "Invalid preconfconfiguration variant: \"%s\"\n", preconfiguration->sval[0]); + if(rank == ROOT) { + fprintf(stderr, "Invalid preconfconfiguration variant: \"%s\"\n", preconfiguration->sval[0]); + } arg_freetable(argtable_fordeletion, sizeof(argtable_fordeletion) / sizeof(argtable_fordeletion[0])); - exit(0); + return parse_outcome::invalid_arguments; } } @@ -172,17 +186,17 @@ int parse_parameters(int argn, char **argv, partition_config.vertex_degree_weights = true; } - if(converter_evaluate->count > 0) { - partition_config.converter_evaluate = true; - } + if(converter_evaluate->count > 0) { + partition_config.converter_evaluate = true; + } - if(save_partition->count > 0) { - partition_config.save_partition = true; - } + if(save_partition->count > 0) { + partition_config.save_partition = true; + } - if(save_partition_binary->count > 0) { - partition_config.save_partition_binary = true; - } + if(save_partition_binary->count > 0) { + partition_config.save_partition_binary = true; + } if(n->count > 0) { partition_config.n = pow(10,n->ival[0]); @@ -201,6 +215,13 @@ int parse_parameters(int argn, char **argv, } if (binary_io_window_size->count > 0) { + if(binary_io_window_size->ival[0] <= 0) { + if(rank == ROOT) { + fprintf(stderr, "Binary I/O window size must be positive.\n"); + } + arg_freetable(argtable_fordeletion, sizeof(argtable_fordeletion) / sizeof(argtable_fordeletion[0])); + return parse_outcome::invalid_arguments; + } partition_config.binary_io_window_size = binary_io_window_size->ival[0]; } @@ -222,8 +243,6 @@ int parse_parameters(int argn, char **argv, if (evolutionary_time_limit->count > 0) { - int size; - MPI_Comm_size( MPI_COMM_WORLD, &size); partition_config.evolutionary_time_limit = evolutionary_time_limit->ival[0]/size; } @@ -250,44 +269,59 @@ int parse_parameters(int argn, char **argv, if(initial_partitioning_algorithm->count > 0) { if(strcmp("kaffpaEstrong", initial_partitioning_algorithm->sval[0]) == 0) { - partition_config.initial_partitioning_algorithm = KAFFPAESTRONG; + partition_config.initial_partitioning_algorithm = + InitialPartitioningAlgorithm::KAFFPAESTRONG; } else if (strcmp("kaffpaEeco",initial_partitioning_algorithm->sval[0]) == 0) { - partition_config.initial_partitioning_algorithm = KAFFPAEECO; + partition_config.initial_partitioning_algorithm = + InitialPartitioningAlgorithm::KAFFPAEECO; } else if (strcmp("kaffpaEfast", initial_partitioning_algorithm->sval[0]) == 0) { - partition_config.initial_partitioning_algorithm = KAFFPAEFAST; + partition_config.initial_partitioning_algorithm = + InitialPartitioningAlgorithm::KAFFPAEFAST; } else if (strcmp("fastsocial", initial_partitioning_algorithm->sval[0]) == 0) { - partition_config.initial_partitioning_algorithm = KAFFPAEFASTSNW; + partition_config.initial_partitioning_algorithm = + InitialPartitioningAlgorithm::KAFFPAEFASTSNW; } else if (strcmp("ecosocial", initial_partitioning_algorithm->sval[0]) == 0) { - partition_config.initial_partitioning_algorithm = KAFFPAEECOSNW; + partition_config.initial_partitioning_algorithm = + InitialPartitioningAlgorithm::KAFFPAEECOSNW; } else if (strcmp("strongsocial", initial_partitioning_algorithm->sval[0]) == 0) { - partition_config.initial_partitioning_algorithm = KAFFPAESTRONGSNW; + partition_config.initial_partitioning_algorithm = + InitialPartitioningAlgorithm::KAFFPAESTRONGSNW; } else if (strcmp("random", initial_partitioning_algorithm->sval[0]) == 0) { - partition_config.initial_partitioning_algorithm = RANDOMIP; + partition_config.initial_partitioning_algorithm = + InitialPartitioningAlgorithm::RANDOMIP; } else { - fprintf(stderr, "Invalid initial partitioning algorithm: \"%s\"\n", initial_partitioning_algorithm->sval[0]); + if(rank == ROOT) { + fprintf(stderr, "Invalid initial partitioning algorithm: \"%s\"\n", initial_partitioning_algorithm->sval[0]); + } arg_freetable(argtable_fordeletion, sizeof(argtable_fordeletion) / sizeof(argtable_fordeletion[0])); - exit(0); + return parse_outcome::invalid_arguments; } } if(node_ordering->count > 0) { if(strcmp("random", node_ordering->sval[0]) == 0) { - partition_config.node_ordering = RANDOM_NODEORDERING; + partition_config.node_ordering = + NodeOrderingType::RANDOM_NODEORDERING; } else if (strcmp("degree", node_ordering->sval[0]) == 0) { - partition_config.node_ordering = DEGREE_NODEORDERING; + partition_config.node_ordering = + NodeOrderingType::DEGREE_NODEORDERING; } else if (strcmp("leastghostnodesfirst_degree", node_ordering->sval[0]) == 0) { - partition_config.node_ordering = LEASTGHOSTNODESFIRST_DEGREE_NODEODERING; + partition_config.node_ordering = + NodeOrderingType::LEASTGHOSTNODESFIRST_DEGREE_NODEODERING; } else if (strcmp("degree_leastghostnodesfirst", node_ordering->sval[0]) == 0) { - partition_config.node_ordering = DEGREE_LEASTGHOSTNODESFIRST_NODEODERING; + partition_config.node_ordering = + NodeOrderingType::DEGREE_LEASTGHOSTNODESFIRST_NODEODERING; } else { - fprintf(stderr, "Invalid node ordering variant: \"%s\"\n", node_ordering->sval[0]); + if(rank == ROOT) { + fprintf(stderr, "Invalid node ordering variant: \"%s\"\n", node_ordering->sval[0]); + } arg_freetable(argtable_fordeletion, sizeof(argtable_fordeletion) / sizeof(argtable_fordeletion[0])); - exit(0); + return parse_outcome::invalid_arguments; } } arg_freetable(argtable_fordeletion, sizeof(argtable_fordeletion) / sizeof(argtable_fordeletion[0])); - return 0; + return parse_outcome::continue_execution; +} } - #endif /* end of include guard: PARSE_PARAMETERS_GPJMGSM8 */ diff --git a/parallel/parallel_src/app/readbgf.cpp b/parallel/parallel_src/app/readbgf.cpp index 89509586..aeeb69bd 100644 --- a/parallel/parallel_src/app/readbgf.cpp +++ b/parallel/parallel_src/app/readbgf.cpp @@ -5,53 +5,40 @@ * Christian Schulz *****************************************************************************/ -#include +#include #include +#include + +#include "communication/mpi_application.h" +#include "configuration.h" #include "io/parallel_graph_io.h" #include "partition_config.h" -#include "configuration.h" - -using namespace std; - -const long fileTypeVersionNumber = 3; -const long header_count = 3; - -int main(int argn, char **argv) -{ - - MPI_Init(&argn, &argv); /* starts MPI */ - - int rank, size; - MPI_Comm_rank( MPI_COMM_WORLD, &rank); - MPI_Comm_size( MPI_COMM_WORLD, &size); - if(argn != 2) { - if( rank == ROOT ) { - std::cout << "usage: " ; - std::cout << "readbgf bfg_file" << std::endl; - } - MPI_Finalize(); - return 0; - } - - - if( rank == ROOT ) { - std::cout << "program reads a BGF (binary graph format) file and prints it into dummy. " << std::endl; - } - string filename(argv[1]); - - configuration cfg; - PPartitionConfig config; - cfg.standard(config); - - parallel_graph_access G; - parallel_graph_io pgio; - pgio.readGraphBinary(config, G, filename, rank, size); - - string output_filename("dummy"); - parallel_graph_io::writeGraphParallelSimple(G, output_filename); - - MPI_Finalize(); - return 0; +int main(int argument_count, char** argument_values) { + using namespace parhip; + mpi::application_runtime runtime{argument_count, argument_values, + "readbgf executable"}; + return runtime.execute([&](mpi::communicator_view communicator) -> int { + auto const rank = communicator.rank(); + auto const size = communicator.size(); + if (argument_count != 2) { + if (rank == ROOT) { + std::cout << "usage: readbgf bgf_file\n"; + } + return EXIT_SUCCESS; + } + if (rank == ROOT) { + std::cout << "program reads a BGF (binary graph format) file and prints " + "it into dummy.\n"; + } + + auto config = PPartitionConfig{}; + auto presets = configuration{}; + presets.standard(config); + auto graph = parallel_graph_access{communicator.native_handle()}; + auto graph_io = parallel_graph_io{}; + graph_io.readGraphBinary(config, graph, argument_values[1], rank, size); + parallel_graph_io::writeGraphParallelSimple(graph, "dummy"); + return EXIT_SUCCESS; + }); } - diff --git a/parallel/parallel_src/app/toolbox.cpp b/parallel/parallel_src/app/toolbox.cpp index 810d58b1..b29bc498 100644 --- a/parallel/parallel_src/app/toolbox.cpp +++ b/parallel/parallel_src/app/toolbox.cpp @@ -6,101 +6,96 @@ *****************************************************************************/ #include + +#include #include -#include -#include -#include -#ifndef _WIN32 -#include -#endif -#include -#include -#include +#include -#include "communication/mpi_tools.h" -#include "communication/dummy_operations.h" +#include "application_math.h" +#include "communication/mpi_application.h" #include "data_structure/parallel_graph_access.h" -#include "distributed_partitioning/distributed_partitioner.h" #include "io/parallel_graph_io.h" #include "io/parallel_vector_io.h" -#include "macros_assertions.h" #include "parse_parameters.h" #include "partition_config.h" -#include "random_functions.h" -#include "timer.h" #include "tools/distributed_quality_metrics.h" -int main(int argn, char **argv) { - - MPI_Init(&argn, &argv); /* starts MPI */ - - PPartitionConfig partition_config; - std::string graph_filename; - - int ret_code = parse_parameters(argn, argv, - partition_config, - graph_filename); - - if(ret_code) { - MPI_Finalize(); - return 0; - } - - int rank, size; - MPI_Comm communicator = MPI_COMM_WORLD; - MPI_Comm_rank( communicator, &rank); - MPI_Comm_size( communicator, &size); - - MPI_Comm_rank( communicator, &rank); - MPI_Comm_size( communicator, &size); - - partition_config.stop_factor /= partition_config.k; - if(rank != 0) partition_config.seed = partition_config.seed*size+rank; - - srand(partition_config.seed); - - parallel_graph_access G(communicator); - parallel_graph_io::readGraphWeighted(partition_config, G, graph_filename, rank, size, communicator); - parallel_vector_io pvio; - pvio.readPartition(partition_config, G, partition_config.input_partition_filename); - - G.printMemoryUsage(std::cout); - - MPI_Barrier(communicator); - - if(partition_config.converter_evaluate) { - distributed_quality_metrics qm; - EdgeWeight edge_cut = qm.edge_cut( G, communicator ); - double balance = qm.balance( partition_config, G, communicator ); - double balance_load = qm.balance_load( partition_config, G, communicator ); - double balance_load_dist = qm.balance_load_dist( partition_config, G, communicator ); - - if( rank == ROOT ) { - std::cout << "log>" << "=====================================" << std::endl; - std::cout << "log>" << "============Evaluation Result========" << std::endl; - std::cout << "log>" << "=====================================" << std::endl; - std::cout << "log>final edge cut " << edge_cut << std::endl; - std::cout << "log>final balance " << balance << std::endl; - std::cout << "log>final balance load " << balance_load << std::endl; - std::cout << "log>final balance load dist " << balance_load_dist << std::endl; - } - qm.comm_vol( partition_config, G, communicator ); - } - - if( partition_config.save_partition ) { - if(rank == ROOT) std::cout << "saving text partition" << std::endl; - parallel_vector_io pvio; - std::string filename("tmppartition.txtp"); - pvio.writePartitionSimpleParallel(G, filename); - } - - if( partition_config.save_partition_binary ) { - if(rank == ROOT) std::cout << "saving binary partition" << std::endl; - parallel_vector_io pvio; - std::string filename("tmppartition.binp"); - pvio.writePartitionBinaryParallelPosix(partition_config, G, filename); - } - - MPI_Barrier(MPI_COMM_WORLD); - MPI_Finalize(); +int main(int argument_count, char** argument_values) { + using namespace parhip; + mpi::application_runtime runtime{argument_count, argument_values, + "ParHIP toolbox executable"}; + return runtime.execute([&](mpi::communicator_view communicator) -> int { + auto partition_config = PPartitionConfig{}; + auto graph_filename = std::string{}; + auto const parse_result = parse_parameters( + argument_count, argument_values, partition_config, graph_filename, + communicator); + if (parse_result != parse_outcome::continue_execution) { + return parse_result == parse_outcome::early_success ? EXIT_SUCCESS + : EXIT_FAILURE; + } + + auto const rank = communicator.rank(); + auto const size = communicator.size(); + auto const native_communicator = communicator.native_handle(); + partition_config.stop_factor /= static_cast(partition_config.k); + auto const seed = application::rank_seed(partition_config.seed, size, rank); + if (!seed.has_value()) { + mpi::abort_on_programming_error(native_communicator, + "invalid rank-specific PRNG seed input"); + } + partition_config.seed = *seed; + std::srand(static_cast(partition_config.seed)); + + auto graph = parallel_graph_access{native_communicator}; + parallel_graph_io::readGraphWeighted(partition_config, graph, + graph_filename, rank, size, + native_communicator); + auto partition_io = parallel_vector_io{}; + partition_io.readPartition(partition_config, graph, + partition_config.input_partition_filename); + graph.printMemoryUsage(std::cout); + + mpi::check_or_abort(MPI_Barrier(native_communicator), native_communicator, + "MPI_Barrier(toolbox input completion)"); + if (partition_config.converter_evaluate) { + auto quality = distributed_quality_metrics{}; + auto const edge_cut = quality.edge_cut(graph, native_communicator); + auto const balance = + quality.balance(partition_config, graph, native_communicator); + auto const balance_load = + quality.balance_load(partition_config, graph, native_communicator); + auto const balance_load_dist = quality.balance_load_dist( + partition_config, graph, native_communicator); + + if (rank == ROOT) { + std::cout << "log>=====================================\n"; + std::cout << "log>============Evaluation Result========\n"; + std::cout << "log>=====================================\n"; + std::cout << "log>final edge cut " << edge_cut << '\n'; + std::cout << "log>final balance " << balance << '\n'; + std::cout << "log>final balance load " << balance_load << '\n'; + std::cout << "log>final balance load dist " << balance_load_dist + << '\n'; + } + quality.comm_vol(partition_config, graph, native_communicator); + } + + if (partition_config.save_partition) { + if (rank == ROOT) { + std::cout << "saving text partition\n"; + } + partition_io.writePartitionSimpleParallel(graph, "tmppartition.txtp"); + } + if (partition_config.save_partition_binary) { + if (rank == ROOT) { + std::cout << "saving binary partition\n"; + } + partition_io.writePartitionBinaryParallelPosix( + partition_config, graph, "tmppartition.binp"); + } + mpi::check_or_abort(MPI_Barrier(native_communicator), native_communicator, + "MPI_Barrier(toolbox completion)"); + return EXIT_SUCCESS; + }); } diff --git a/parallel/parallel_src/cmake/kahip_mpi_capabilities.h.in b/parallel/parallel_src/cmake/kahip_mpi_capabilities.h.in new file mode 100644 index 00000000..d43ff23e --- /dev/null +++ b/parallel/parallel_src/cmake/kahip_mpi_capabilities.h.in @@ -0,0 +1,10 @@ +#pragma once + +#cmakedefine01 KAHIP_HAVE_MPI_ALLTOALLV_C +#cmakedefine01 KAHIP_HAVE_MPI_ALLREDUCE_C +#cmakedefine01 KAHIP_HAVE_MPI_REDUCE_C +#cmakedefine01 KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C +#cmakedefine01 KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV +#cmakedefine01 KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV_C +#cmakedefine01 KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT +#cmakedefine01 KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT_C diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/LICENSE b/parallel/parallel_src/extern/argtable3-3.2.2/LICENSE deleted file mode 100644 index 72cbcbc2..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/LICENSE +++ /dev/null @@ -1,167 +0,0 @@ -Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of STEWART HEITMANN nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -FreeBSD getopt library -====================== - -Copyright (c) 2000 The NetBSD Foundation, Inc. -All rights reserved. - -This code is derived from software contributed to The NetBSD Foundation -by Dieter Baron and Thomas Klausner. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS -``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED -TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - - -Tcl library -=========== - -This software is copyrighted by the Regents of the University of -California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState -Corporation and other parties. The following terms apply to all files -associated with the software unless explicitly disclaimed in -individual files. - -The authors hereby grant permission to use, copy, modify, distribute, -and license this software and its documentation for any purpose, provided -that existing copyright notices are retained in all copies and that this -notice is included verbatim in any distributions. No written agreement, -license, or royalty fee is required for any of the authorized uses. -Modifications to this software may be copyrighted by their authors -and need not follow the licensing terms described here, provided that -the new terms are clearly indicated on the first page of each file where -they apply. - -IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY -FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES -ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY -DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - -THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE -IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE -NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR -MODIFICATIONS. - -GOVERNMENT USE: If you are acquiring this software on behalf of the -U.S. government, the Government shall have only "Restricted Rights" -in the software and related documentation as defined in the Federal -Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you -are acquiring the software on behalf of the Department of Defense, the -software shall be classified as "Commercial Computer Software" and the -Government shall have only "Restricted Rights" as defined in Clause -252.227-7014 (b) (3) of DFARs. Notwithstanding the foregoing, the -authors grant the U.S. Government and others acting in its behalf -permission to use and distribute the software in accordance with the -terms specified in this license. - - -C Hash Table library -==================== - -Copyright (c) 2002, Christopher Clark -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -* Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -* Neither the name of the original author; nor the names of any contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER -OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -The Better String library -========================= - -Copyright (c) 2014, Paul Hsieh -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of bstrlib nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/README.md b/parallel/parallel_src/extern/argtable3-3.2.2/README.md deleted file mode 100644 index 31376c2f..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/README.md +++ /dev/null @@ -1,399 +0,0 @@ -[![Build Status](https://travis-ci.org/argtable/argtable3.svg?branch=master)](https://travis-ci.org/argtable/argtable3) -[![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) - - -Introduction of Argtable3 -========================= - -**Argtable3** is an open source ANSI C library that parses GNU-style -command-line options with the `getopt` library. It simplifies command-line -parsing by defining a declarative-style API that you can use to specify what -your command-line syntax looks like. Argtable3 will automatically generate -consistent error handling logic and textual descriptions of the command line -syntax, which are essential but tedious to implement for a robust CLI program. - - -Quick Start ------------ - -You can embed the amalgamation source files in your projects, add Argtable3 as a -dependency in the vcpkg manifest, install Argtable3 as a system-wide CMake -package, or build the library from release archives. - -### Embed Amalgamation Source Files - -> We no longer provide the amalgamation source files (`argtable3.c` and -> `argtable3.h`) in the repository. You can get the amalgamation distribution -> either from the release page (`argtable--amalgamation.(zip|tar.gz)`), -> or generate the distribution yourself by using the generator under the `tools` -> directory: -> -> 1. Navigate to the `tools` directory. -> 2. Run `./build dist`, which will generate the distribution under the `/dist` -> directory. - -Add `argtable3.c` and `argtable3.h` from the amalgamation distribution to your -projects. This is the simplest and recommended way to use Argtable3: it not only -removes the hassle of building the library, but also allows compilers to do -better inter-procedure optimization. - - -### Install for a Single Project with vcpkg Manifest - -[vcpkg](https://vcpkg.io) is an open source C/C++ package manager based on -CMake, and it supports certain stable releases of Argtable3. To add the library -to your CMake project, it's recommended to add vcpkg as a submodule to your -project repo and use it to manage project dependencies. All libraries installed -in this way can only be consumed by the project and won't impact other projects -in the system. - -If your project is under `D:/projects/demo` and the vcpkg submodule is under -`D:/projects/demo/deps/vcpkg`, first you need to add Argtable3 to the manifest, -`D:/projects/demo/vcpkg.json`: -``` -{ - "name": "demo", - "version": "0.0.1", - "dependencies": [ - { - "name": "argtable3", - "version>=": "3.2.1" - } - ], - "builtin-baseline": "92b42c4c680defe94f1665a847d04ded890f372e" -} -``` - -To add Argtable3 to your CMake scripts, you need to integrate the local vcpkg to -CMake by setting the `CMAKE_TOOLCHAIN_FILE` variable. You also need to link to -the static VC runtime (`/MT` or `/MTd`) if you want to use the static library -version of Argtable3: -``` -cmake_minimum_required(VERSION 3.18) - -set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/deps/vcpkg/scripts/buildsystems/vcpkg.cmake - CACHE STRING "Vcpkg toolchain file") - -project(versionstest) - -add_executable(main main.cpp) - -find_package(Argtable3 CONFIG REQUIRED) -target_link_libraries(main PRIVATE argtable3::argtable3) - -if(VCPKG_TARGET_TRIPLET STREQUAL "x64-windows-static") - set_property(TARGET main PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") -endif() -``` - -Now you can run `cmake` to install Argtable3, configure and generate build -scripts, and build the project: -``` -$ mkdir build -$ cd build -$ cmake .. -DVCPKG_TARGET_TRIPLET=x64-windows-static -$ cmake --build . -``` - -### Install for All Projects with vcpkg - -If you want to make Argtable3 available for all projects in the system, you can -clone vcpkg to any directory and install packages there. Assuming vcpkg has been -cloned in `D:/dev/vcpkg` and the directory has been added to `PATH`, you can -install the static library version of Argtable3 in `D:/dev/vcpkg/installed`: -``` -$ vcpkg install argtable3:x64-windows-static -``` - -Since each developer may clone vcpkg in a different place, it may not be -appropriate to specify the `CMAKE_TOOLCHAIN_FILE` variable in `CMakeLists.txt`. -Therefore, you should remove setting the `CMAKE_TOOLCHAIN_FILE` variable in the -`CMakeLists.txt` example above, and set the variable in the command line: -``` -$ mkdir build -$ cd build -$ cmake .. -DVCPKG_TARGET_TRIPLET=x64-windows-static -DCMAKE_TOOLCHAIN_FILE=D:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake -$ cmake --build . -``` - - -### Build from Release Archives or Source - -If none of the methods above suits your needs, or if you want to help developing -Argtable3, you can always build from archives on the release page or from the -repository. - -* If you use GCC (Linux, MacOSX, MinGW, Cygwin), run: - - ``` - $ mkdir build - $ cd build - $ cmake -DCMAKE_BUILD_TYPE=Debug .. - $ make - $ make test - ``` - - Makefile-based generators in CMake only support one configuration at a time, - so you need to specify `CMAKE_BUILD_TYPE` to `Debug`, `Release`, `MinSizeRel`, - or `RelWithDebInfo`. To build multiple configurations, you need to create a - build directory for each configuraiton. - - Since v3.2.1, CMake scripts will check `BUILD_SHARED_LIBS` and build either - the static library or the dynamic library at a time. `BUILD_SHARED_LIBS` is - `OFF` by default, so if you want to build the dynamic library, you have to set - `BUILD_SHARED_LIBS` to `ON` explicitly: - - ``` - $ cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=ON .. - ``` - - To cleanup, run `make clean` or remove the build directory: - - ``` - $ rm -rf build - ``` - -* If you use Microsoft Visual C++ compiler, run: - - ``` - $ mkdir build - $ cd build - $ cmake -G "Visual Studio 15 2017 Win64" .. - $ cmake --build . --config Debug - $ ctest -C Debug - ``` - - You can also use Visual Studio 2017 IDE to open the generated solution. To - cleanup, just remove the `build` directory. - - -To build a tagged version, go to the project root directory, and use the -`Makefile` in the project root folder to check out the specified version: - - ``` - $ make taglist - Available TAGs: - v3.1.1.432a160 - $ make co TAG=v3.1.1.432a160 - $ cd .tags/v3.1.1.432a160 - $ mkdir build - $ cd build - $ cmake .. - $ make - $ make test - ``` - -You will find the shared library (or Windows DLL), static library, and the -amalgamation distribution under the build directory. - - -Documentation -------------- - -To learn how to use the Argtable3 API, you can see the documentation on the web -site, study examples in the `examples` directory, or even check the unit tests -in the `tests` directory. - -To build a local copy of the documentation, you need to install the following -tools: - -* [Sphinx](https://www.sphinx-doc.org): A documentation generator based on the - reStructuredText markup format. -* [Read the Docs Sphinx Theme](https://sphinx-rtd-theme.readthedocs.io): A - Sphinx theme designed to look modern and be mobile-friendly. -* [Breathe](https://breathe.readthedocs.io): A bridge between the Sphinx and - Doxygen documentation systems. -* [Doxygen](http://www.doxygen.nl/): A documentation generator for C/C++ - sources. - -Go to the `docs` directory and run the `doxygen` command to generate Doxygen XML -output, which will be saved in the `docs/xml` directory: - -``` -$ doxygen -``` - -Run the `make` batch script and you will see the documentation in the -`docs/_build/html` directory. - -``` -$ make html -``` - - -Unit Tests ----------- - -Argtable3 is a BSD-licensed open source library, so you can modify the library -anyway you want. However, before committing your code to your own repository or -the Argtable3 official repository, please make sure your changes won't cause any -compiler warning and can pass the unit tests included in the distribution. - -To build and test each configuration (`Debug`, `Release`, `MinSizeRel`, -`RelWithDebInfo`), you can run CMake and CTest on all supported platforms: - -``` -$ mkdir build_debug && cd build_debug -$ cmake -DCMAKE_BUILD_TYPE=Debug .. -$ cmake --build . --config Debug -$ ctest -C Debug - -$ cd .. && mkdir build_release && cd build_release -$ cmake -DCMAKE_BUILD_TYPE=Release .. -$ cmake --build . --config Release -$ ctest -C Release - -$ cd .. && mkdir build_minsizerel && cd build_minsizerel -$ cmake -DCMAKE_BUILD_TYPE=MinSizeRel .. -$ cmake --build . --config MinSizeRel -$ ctest -C MinSizeRel - -$ cd .. && mkdir build_relwithdebinfo && cd build_relwithdebinfo -$ cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo .. -$ cmake --build . --config RelWithDebInfo -$ ctest -C RelWithDebInfo -``` - -If you see the following screenshot, you know that some unit tests are broken: - -``` -$ make test -Running tests... -Test project ~/Projects/argtable3/build-gcc-release - Start 1: test_shared -1/4 Test #1: test_shared ......................***Failed 0.07 sec - Start 2: test_static -2/4 Test #2: test_static ......................***Failed 0.13 sec - Start 3: test_src -3/4 Test #3: test_src .........................***Failed 0.13 sec - Start 4: test_amalgamation -4/4 Test #4: test_amalgamation ................***Failed 0.14 sec - -0% tests passed, 4 tests failed out of 4 - -Total Test time (real) = 0.48 sec - -The following tests FAILED: - 1 - test_shared (Failed) - 2 - test_static (Failed) - 3 - test_src (Failed) - 4 - test_amalgamation (Failed) -Errors while running CTest -make: *** [Makefile:97: test] Error 8 -``` - -To understand which unit tests are broken, you need to run the failed test -programs (based on CuTest) directly: - -``` -$ ./tests/test_shared -....................................................................................... -...................................................................F............. - -There was 1 failure: -1) test_argdstr_basic_001: ~/Projects/argtable3/tests/testargdstr.c:51: assert failed - -!!!FAILURES!!! -Runs: 168 Passes: 167 Fails: 1 -``` - - -Memory Issue Detection with ASan and Valgrind ---------------------------------------------- - -In order to prevent common memory issues in C, such as memory leak and buffer -overflow, we should use [ASan -(AddressSanitizer)](https://en.wikipedia.org/wiki/AddressSanitizer) and -[Valgrind](https://en.wikipedia.org/wiki/Valgrind) to detect as many -memory-related problems as possible before committing our code. - -To use ASan, we need to add `-fsanitize=address` to the `CFLAGS` variable when -we run `cmake` to build the **Debug** version. We should use the Debug version -because CMake will add `-g` to `CFLAGS` and prevent optimizing the code, so we -can see accurate file names and line numbers in ASan error messages. After -building the code, set the `CTEST_OUTPUT_ON_FAILURE` variable to `1` to output -error messages when we run unit tests: - -``` -$ mkdir build -$ cd build -$ CFLAGS="-fsanitize=address" cmake -DCMAKE_BUILD_TYPE=Debug .. -$ make -$ CTEST_OUTPUT_ON_FAILURE=1 make test -Running tests... -Test project /home/tomghuang/Projects/argtable3/build - Start 1: test_shared -1/4 Test #1: test_shared ...................... Passed 3.45 sec - Start 2: test_static -2/4 Test #2: test_static ...................... Passed 3.31 sec - Start 3: test_src -3/4 Test #3: test_src ......................... Passed 3.06 sec - Start 4: test_amalgamation -4/4 Test #4: test_amalgamation ................ Passed 3.29 sec - -100% tests passed, 0 tests failed out of 4 - -Total Test time (real) = 13.12 sec -``` - -To use Valgrind, just use `valgrind` to run the unit test programs: - -``` -$ valgrind ./tests/test_src -==23290== Memcheck, a memory error detector -==23290== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al. -==23290== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info -==23290== Command: ./test_src -==23290== -...................................................................................... -.................................................................................. - -OK (168 tests) - -==23290== -==23290== HEAP SUMMARY: -==23290== in use at exit: 0 bytes in 0 blocks -==23290== total heap usage: 102,085 allocs, 102,085 frees, 5,589,475 bytes allocated -==23290== -==23290== All heap blocks were freed -- no leaks are possible -==23290== -==23290== For counts of detected and suppressed errors, rerun with: -v -==23290== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) -``` - - -Authors -------- - -Argtable is Copyright (C) 1998-2001,2003-2011 Stewart Heitmann. -Parts are Copyright (C) 1989-1994, 1996-1999, 2001, 2003 - Free Software Foundation, Inc. - -Argtable was written by Stewart Heitmann - -Argtable is now maintained by Tom G. Huang -The project homepage of argtable 3.x is http://www.argtable.org -The project homepage of argtable 2.x is http://argtable.sourceforge.net/ - -Here is a list of contributors who have helped to improve argtable: - -- **Nina Clemson**: Editing the original argtable-1.0 documentation. -- **Livio Bertacco**: For bug fixes and the argtable-2.x Visual C++ Makefiles. -- **Justin Dearing**: For bug fixes and Windows DLL support, plus code support for the Open Watcom compiler and help with the Mac OS X configuration. -- **Asa Packer**: Contributing bug fixes and upgrades to the Visual C++ Makefiles. -- **Danilo Cicerone**: For the Italian translation of "Introduction to Argtable-2x" on http://www.digitazero.org. -- **Uli Fouquet**: For configuration patches and documentation related to cross-compiling argtable from Unix to Windows, as well as providing the arg_print_glossary_gnu function. -- **Shachar Shemesh**: For Debian package integration and kick-starting the migration to automake/autoconf. -- **Jasper Lievisse Adriaanse**: Maintaining the argtable package in OpenBSD ports. -- **Ulrich Mohr**: For bug fixes relating to Texas Instrument DSP platforms. -- **John Vickers**: For bug fixes relating to Solaris/Motorola platforms. -- **Steve O'Neil**: For bug fixes relating to Solaris/Motorola platforms. -- **Lori A. Pritchett-Sheats**: Fixing a makefile bug relating to "make dist". -- **Paolo Bormida**: For instructions on building argtable with date and regex support on Windows. -- **Michel Valin**: For bug fixes relating to the configure scripts on IBM AIX platforms and instructions on compiling the example code under AIX. -- **Steve Christensen**: Providing prebuilt packages for SPARC/Solaris and x86/Solaris platforms on www.sunfreeware.com. -- **Jess Portnoy**: Reworking the rpm package and integrating argtable into Fedora Linux. -- **Michael Brown**: Incorporating support for pkg-config into the autoconf scripts. -- **Alexander Lindert**: For extensions to the parser to support hex, octal and binary integer formats as well as KB/MB/GB suffixes. -- **Rob Zaborowski**: Providing build configuration files for CMake. -- **Moczik Gabor**: For bug fixes relating to the parsing of filepaths and filename extensions. diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/argtable3.c b/parallel/parallel_src/extern/argtable3-3.2.2/argtable3.c deleted file mode 100644 index dcc8e769..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/argtable3.c +++ /dev/null @@ -1,6021 +0,0 @@ -/******************************************************************************* - * This file is part of the argtable3 library. - * - * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include "argtable3.h" - -#define ARG_AMALGAMATION - -/******************************************************************************* - * argtable3_private: Declares private types, constants, and interfaces - * - * This file is part of the argtable3 library. - * - * Copyright (C) 2013-2019 Tom G. Huang - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#ifndef ARG_UTILS_H -#define ARG_UTILS_H - -#include - -#define ARG_ENABLE_TRACE 0 -#define ARG_ENABLE_LOG 1 - -#ifdef __cplusplus -extern "C" { -#endif - -enum { ARG_ERR_MINCOUNT = 1, ARG_ERR_MAXCOUNT, ARG_ERR_BADINT, ARG_ERR_OVERFLOW, ARG_ERR_BADDOUBLE, ARG_ERR_BADDATE, ARG_ERR_REGNOMATCH }; - -typedef void(arg_panicfn)(const char* fmt, ...); - -#if defined(_MSC_VER) -#define ARG_TRACE(x) \ - __pragma(warning(push)) __pragma(warning(disable : 4127)) do { \ - if (ARG_ENABLE_TRACE) \ - dbg_printf x; \ - } \ - while (0) \ - __pragma(warning(pop)) - -#define ARG_LOG(x) \ - __pragma(warning(push)) __pragma(warning(disable : 4127)) do { \ - if (ARG_ENABLE_LOG) \ - dbg_printf x; \ - } \ - while (0) \ - __pragma(warning(pop)) -#else -#define ARG_TRACE(x) \ - do { \ - if (ARG_ENABLE_TRACE) \ - dbg_printf x; \ - } while (0) - -#define ARG_LOG(x) \ - do { \ - if (ARG_ENABLE_LOG) \ - dbg_printf x; \ - } while (0) -#endif - -/* - * Rename a few generic names to unique names. - * They can be a problem for the platforms like NuttX, where - * the namespace is flat for everything including apps and libraries. - */ -#define xmalloc argtable3_xmalloc -#define xcalloc argtable3_xcalloc -#define xrealloc argtable3_xrealloc -#define xfree argtable3_xfree - -extern void dbg_printf(const char* fmt, ...); -extern void arg_set_panic(arg_panicfn* proc); -extern void* xmalloc(size_t size); -extern void* xcalloc(size_t count, size_t size); -extern void* xrealloc(void* ptr, size_t size); -extern void xfree(void* ptr); - -struct arg_hashtable_entry { - void *k, *v; - unsigned int h; - struct arg_hashtable_entry* next; -}; - -typedef struct arg_hashtable { - unsigned int tablelength; - struct arg_hashtable_entry** table; - unsigned int entrycount; - unsigned int loadlimit; - unsigned int primeindex; - unsigned int (*hashfn)(const void* k); - int (*eqfn)(const void* k1, const void* k2); -} arg_hashtable_t; - -/** - * @brief Create a hash table. - * - * @param minsize minimum initial size of hash table - * @param hashfn function for hashing keys - * @param eqfn function for determining key equality - * @return newly created hash table or NULL on failure - */ -arg_hashtable_t* arg_hashtable_create(unsigned int minsize, unsigned int (*hashfn)(const void*), int (*eqfn)(const void*, const void*)); - -/** - * @brief This function will cause the table to expand if the insertion would take - * the ratio of entries to table size over the maximum load factor. - * - * This function does not check for repeated insertions with a duplicate key. - * The value returned when using a duplicate key is undefined -- when - * the hash table changes size, the order of retrieval of duplicate key - * entries is reversed. - * If in doubt, remove before insert. - * - * @param h the hash table to insert into - * @param k the key - hash table claims ownership and will free on removal - * @param v the value - does not claim ownership - * @return non-zero for successful insertion - */ -void arg_hashtable_insert(arg_hashtable_t* h, void* k, void* v); - -#define ARG_DEFINE_HASHTABLE_INSERT(fnname, keytype, valuetype) \ - int fnname(arg_hashtable_t* h, keytype* k, valuetype* v) { return arg_hashtable_insert(h, k, v); } - -/** - * @brief Search the specified key in the hash table. - * - * @param h the hash table to search - * @param k the key to search for - does not claim ownership - * @return the value associated with the key, or NULL if none found - */ -void* arg_hashtable_search(arg_hashtable_t* h, const void* k); - -#define ARG_DEFINE_HASHTABLE_SEARCH(fnname, keytype, valuetype) \ - valuetype* fnname(arg_hashtable_t* h, keytype* k) { return (valuetype*)(arg_hashtable_search(h, k)); } - -/** - * @brief Remove the specified key from the hash table. - * - * @param h the hash table to remove the item from - * @param k the key to search for - does not claim ownership - */ -void arg_hashtable_remove(arg_hashtable_t* h, const void* k); - -#define ARG_DEFINE_HASHTABLE_REMOVE(fnname, keytype, valuetype) \ - void fnname(arg_hashtable_t* h, keytype* k) { arg_hashtable_remove(h, k); } - -/** - * @brief Return the number of keys in the hash table. - * - * @param h the hash table - * @return the number of items stored in the hash table - */ -unsigned int arg_hashtable_count(arg_hashtable_t* h); - -/** - * @brief Change the value associated with the key. - * - * function to change the value associated with a key, where there already - * exists a value bound to the key in the hash table. - * Source due to Holger Schemel. - * - * @name hashtable_change - * @param h the hash table - * @param key - * @param value - */ -int arg_hashtable_change(arg_hashtable_t* h, void* k, void* v); - -/** - * @brief Free the hash table and the memory allocated for each key-value pair. - * - * @param h the hash table - * @param free_values whether to call 'free' on the remaining values - */ -void arg_hashtable_destroy(arg_hashtable_t* h, int free_values); - -typedef struct arg_hashtable_itr { - arg_hashtable_t* h; - struct arg_hashtable_entry* e; - struct arg_hashtable_entry* parent; - unsigned int index; -} arg_hashtable_itr_t; - -arg_hashtable_itr_t* arg_hashtable_itr_create(arg_hashtable_t* h); - -void arg_hashtable_itr_destroy(arg_hashtable_itr_t* itr); - -/** - * @brief Return the value of the (key,value) pair at the current position. - */ -extern void* arg_hashtable_itr_key(arg_hashtable_itr_t* i); - -/** - * @brief Return the value of the (key,value) pair at the current position. - */ -extern void* arg_hashtable_itr_value(arg_hashtable_itr_t* i); - -/** - * @brief Advance the iterator to the next element. Returns zero if advanced to end of table. - */ -int arg_hashtable_itr_advance(arg_hashtable_itr_t* itr); - -/** - * @brief Remove current element and advance the iterator to the next element. - */ -int arg_hashtable_itr_remove(arg_hashtable_itr_t* itr); - -/** - * @brief Search and overwrite the supplied iterator, to point to the entry matching the supplied key. - * - * @return Zero if not found. - */ -int arg_hashtable_itr_search(arg_hashtable_itr_t* itr, arg_hashtable_t* h, void* k); - -#define ARG_DEFINE_HASHTABLE_ITERATOR_SEARCH(fnname, keytype) \ - int fnname(arg_hashtable_itr_t* i, arg_hashtable_t* h, keytype* k) { return (arg_hashtable_iterator_search(i, h, k)); } - -#ifdef __cplusplus -} -#endif - -#endif -/******************************************************************************* - * arg_utils: Implements memory, panic, and other utility functions - * - * This file is part of the argtable3 library. - * - * Copyright (C) 2013-2019 Tom G. Huang - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include "argtable3.h" - -#ifndef ARG_AMALGAMATION -#include "argtable3_private.h" -#endif - -#include -#include -#include -#include - -static void panic(const char* fmt, ...); -static arg_panicfn* s_panic = panic; - -void dbg_printf(const char* fmt, ...) { - va_list args; - va_start(args, fmt); - vfprintf(stderr, fmt, args); - va_end(args); -} - -static void panic(const char* fmt, ...) { - va_list args; - char* s; - - va_start(args, fmt); - vfprintf(stderr, fmt, args); - va_end(args); - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable : 4996) -#endif - s = getenv("EF_DUMPCORE"); -#if defined(_MSC_VER) -#pragma warning(pop) -#endif - - if (s != NULL && *s != '\0') { - abort(); - } else { - exit(EXIT_FAILURE); - } -} - -void arg_set_panic(arg_panicfn* proc) { - s_panic = proc; -} - -void* xmalloc(size_t size) { - void* ret = malloc(size); - if (!ret) { - s_panic("Out of memory!\n"); - } - return ret; -} - -void* xcalloc(size_t count, size_t size) { - size_t allocated_count = count && size ? count : 1; - size_t allocated_size = count && size ? size : 1; - void* ret = calloc(allocated_count, allocated_size); - if (!ret) { - s_panic("Out of memory!\n"); - } - return ret; -} - -void* xrealloc(void* ptr, size_t size) { - size_t allocated_size = size ? size : 1; - void* ret = realloc(ptr, allocated_size); - if (!ret) { - s_panic("Out of memory!\n"); - } - return ret; -} - -void xfree(void* ptr) { - free(ptr); -} - -static void merge(void* data, int esize, int i, int j, int k, arg_comparefn* comparefn) { - char* a = (char*)data; - char* m; - int ipos, jpos, mpos; - - /* Initialize the counters used in merging. */ - ipos = i; - jpos = j + 1; - mpos = 0; - - /* Allocate storage for the merged elements. */ - m = (char*)xmalloc((size_t)(esize * ((k - i) + 1))); - - /* Continue while either division has elements to merge. */ - while (ipos <= j || jpos <= k) { - if (ipos > j) { - /* The left division has no more elements to merge. */ - while (jpos <= k) { - memcpy(&m[mpos * esize], &a[jpos * esize], (size_t)esize); - jpos++; - mpos++; - } - - continue; - } else if (jpos > k) { - /* The right division has no more elements to merge. */ - while (ipos <= j) { - memcpy(&m[mpos * esize], &a[ipos * esize], (size_t)esize); - ipos++; - mpos++; - } - - continue; - } - - /* Append the next ordered element to the merged elements. */ - if (comparefn(&a[ipos * esize], &a[jpos * esize]) < 0) { - memcpy(&m[mpos * esize], &a[ipos * esize], (size_t)esize); - ipos++; - mpos++; - } else { - memcpy(&m[mpos * esize], &a[jpos * esize], (size_t)esize); - jpos++; - mpos++; - } - } - - /* Prepare to pass back the merged data. */ - memcpy(&a[i * esize], m, (size_t)(esize * ((k - i) + 1))); - xfree(m); -} - -void arg_mgsort(void* data, int size, int esize, int i, int k, arg_comparefn* comparefn) { - int j; - - /* Stop the recursion when no more divisions can be made. */ - if (i < k) { - /* Determine where to divide the elements. */ - j = (int)(((i + k - 1)) / 2); - - /* Recursively sort the two divisions. */ - arg_mgsort(data, size, esize, i, j, comparefn); - arg_mgsort(data, size, esize, j + 1, k, comparefn); - merge(data, esize, i, j, k, comparefn); - } -} -/******************************************************************************* - * arg_hashtable: Implements the hash table utilities - * - * This file is part of the argtable3 library. - * - * Copyright (C) 2013-2019 Tom G. Huang - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#ifndef ARG_AMALGAMATION -#include "argtable3_private.h" -#endif - -#include -#include -#include -#include - -/* - * This hash table module is adapted from the C hash table implementation by - * Christopher Clark. Here is the copyright notice from the library: - * - * Copyright (c) 2002, Christopher Clark - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of the original author; nor the names of any contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER - * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/* - * Credit for primes table: Aaron Krowne - * http://br.endernet.org/~akrowne/ - * http://planetmath.org/encyclopedia/GoodHashTablePrimes.html - */ -static const unsigned int primes[] = {53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, - 24593, 49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, - 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741}; -const unsigned int prime_table_length = sizeof(primes) / sizeof(primes[0]); -const float max_load_factor = (float)0.65; - -static unsigned int enhanced_hash(arg_hashtable_t* h, const void* k) { - /* - * Aim to protect against poor hash functions by adding logic here. - * The logic is taken from Java 1.4 hash table source. - */ - unsigned int i = h->hashfn(k); - i += ~(i << 9); - i ^= ((i >> 14) | (i << 18)); /* >>> */ - i += (i << 4); - i ^= ((i >> 10) | (i << 22)); /* >>> */ - return i; -} - -static unsigned int index_for(unsigned int tablelength, unsigned int hashvalue) { - return (hashvalue % tablelength); -} - -arg_hashtable_t* arg_hashtable_create(unsigned int minsize, unsigned int (*hashfn)(const void*), int (*eqfn)(const void*, const void*)) { - arg_hashtable_t* h; - unsigned int pindex; - unsigned int size = primes[0]; - - /* Check requested hash table isn't too large */ - if (minsize > (1u << 30)) - return NULL; - - /* - * Enforce size as prime. The reason is to avoid clustering of values - * into a small number of buckets (yes, distribution). A more even - * distributed hash table will perform more consistently. - */ - for (pindex = 0; pindex < prime_table_length; pindex++) { - if (primes[pindex] > minsize) { - size = primes[pindex]; - break; - } - } - - h = (arg_hashtable_t*)xmalloc(sizeof(arg_hashtable_t)); - h->table = (struct arg_hashtable_entry**)xmalloc(sizeof(struct arg_hashtable_entry*) * size); - memset(h->table, 0, size * sizeof(struct arg_hashtable_entry*)); - h->tablelength = size; - h->primeindex = pindex; - h->entrycount = 0; - h->hashfn = hashfn; - h->eqfn = eqfn; - h->loadlimit = (unsigned int)ceil(size * (double)max_load_factor); - return h; -} - -static int arg_hashtable_expand(arg_hashtable_t* h) { - /* Double the size of the table to accommodate more entries */ - struct arg_hashtable_entry** newtable; - struct arg_hashtable_entry* e; - unsigned int newsize; - unsigned int i; - unsigned int index; - - /* Check we're not hitting max capacity */ - if (h->primeindex == (prime_table_length - 1)) - return 0; - newsize = primes[++(h->primeindex)]; - - newtable = (struct arg_hashtable_entry**)xmalloc(sizeof(struct arg_hashtable_entry*) * newsize); - memset(newtable, 0, newsize * sizeof(struct arg_hashtable_entry*)); - /* - * This algorithm is not 'stable': it reverses the list - * when it transfers entries between the tables - */ - for (i = 0; i < h->tablelength; i++) { - while (NULL != (e = h->table[i])) { - h->table[i] = e->next; - index = index_for(newsize, e->h); - e->next = newtable[index]; - newtable[index] = e; - } - } - - xfree(h->table); - h->table = newtable; - h->tablelength = newsize; - h->loadlimit = (unsigned int)ceil(newsize * (double)max_load_factor); - return -1; -} - -unsigned int arg_hashtable_count(arg_hashtable_t* h) { - return h->entrycount; -} - -void arg_hashtable_insert(arg_hashtable_t* h, void* k, void* v) { - /* This method allows duplicate keys - but they shouldn't be used */ - unsigned int index; - struct arg_hashtable_entry* e; - if ((h->entrycount + 1) > h->loadlimit) { - /* - * Ignore the return value. If expand fails, we should - * still try cramming just this value into the existing table - * -- we may not have memory for a larger table, but one more - * element may be ok. Next time we insert, we'll try expanding again. - */ - arg_hashtable_expand(h); - } - e = (struct arg_hashtable_entry*)xmalloc(sizeof(struct arg_hashtable_entry)); - e->h = enhanced_hash(h, k); - index = index_for(h->tablelength, e->h); - e->k = k; - e->v = v; - e->next = h->table[index]; - h->table[index] = e; - h->entrycount++; -} - -void* arg_hashtable_search(arg_hashtable_t* h, const void* k) { - struct arg_hashtable_entry* e; - unsigned int hashvalue; - unsigned int index; - - hashvalue = enhanced_hash(h, k); - index = index_for(h->tablelength, hashvalue); - e = h->table[index]; - while (e != NULL) { - /* Check hash value to short circuit heavier comparison */ - if ((hashvalue == e->h) && (h->eqfn(k, e->k))) - return e->v; - e = e->next; - } - return NULL; -} - -void arg_hashtable_remove(arg_hashtable_t* h, const void* k) { - /* - * TODO: consider compacting the table when the load factor drops enough, - * or provide a 'compact' method. - */ - - struct arg_hashtable_entry* e; - struct arg_hashtable_entry** pE; - unsigned int hashvalue; - unsigned int index; - - hashvalue = enhanced_hash(h, k); - index = index_for(h->tablelength, hashvalue); - pE = &(h->table[index]); - e = *pE; - while (NULL != e) { - /* Check hash value to short circuit heavier comparison */ - if ((hashvalue == e->h) && (h->eqfn(k, e->k))) { - *pE = e->next; - h->entrycount--; - xfree(e->k); - xfree(e->v); - xfree(e); - return; - } - pE = &(e->next); - e = e->next; - } -} - -void arg_hashtable_destroy(arg_hashtable_t* h, int free_values) { - unsigned int i; - struct arg_hashtable_entry *e, *f; - struct arg_hashtable_entry** table = h->table; - if (free_values) { - for (i = 0; i < h->tablelength; i++) { - e = table[i]; - while (NULL != e) { - f = e; - e = e->next; - xfree(f->k); - xfree(f->v); - xfree(f); - } - } - } else { - for (i = 0; i < h->tablelength; i++) { - e = table[i]; - while (NULL != e) { - f = e; - e = e->next; - xfree(f->k); - xfree(f); - } - } - } - xfree(h->table); - xfree(h); -} - -arg_hashtable_itr_t* arg_hashtable_itr_create(arg_hashtable_t* h) { - unsigned int i; - unsigned int tablelength; - - arg_hashtable_itr_t* itr = (arg_hashtable_itr_t*)xmalloc(sizeof(arg_hashtable_itr_t)); - itr->h = h; - itr->e = NULL; - itr->parent = NULL; - tablelength = h->tablelength; - itr->index = tablelength; - if (0 == h->entrycount) - return itr; - - for (i = 0; i < tablelength; i++) { - if (h->table[i] != NULL) { - itr->e = h->table[i]; - itr->index = i; - break; - } - } - return itr; -} - -void arg_hashtable_itr_destroy(arg_hashtable_itr_t* itr) { - xfree(itr); -} - -void* arg_hashtable_itr_key(arg_hashtable_itr_t* i) { - return i->e->k; -} - -void* arg_hashtable_itr_value(arg_hashtable_itr_t* i) { - return i->e->v; -} - -int arg_hashtable_itr_advance(arg_hashtable_itr_t* itr) { - unsigned int j; - unsigned int tablelength; - struct arg_hashtable_entry** table; - struct arg_hashtable_entry* next; - - if (itr->e == NULL) - return 0; /* stupidity check */ - - next = itr->e->next; - if (NULL != next) { - itr->parent = itr->e; - itr->e = next; - return -1; - } - - tablelength = itr->h->tablelength; - itr->parent = NULL; - if (tablelength <= (j = ++(itr->index))) { - itr->e = NULL; - return 0; - } - - table = itr->h->table; - while (NULL == (next = table[j])) { - if (++j >= tablelength) { - itr->index = tablelength; - itr->e = NULL; - return 0; - } - } - - itr->index = j; - itr->e = next; - return -1; -} - -int arg_hashtable_itr_remove(arg_hashtable_itr_t* itr) { - struct arg_hashtable_entry* remember_e; - struct arg_hashtable_entry* remember_parent; - int ret; - - /* Do the removal */ - if ((itr->parent) == NULL) { - /* element is head of a chain */ - itr->h->table[itr->index] = itr->e->next; - } else { - /* element is mid-chain */ - itr->parent->next = itr->e->next; - } - /* itr->e is now outside the hashtable */ - remember_e = itr->e; - itr->h->entrycount--; - xfree(remember_e->k); - xfree(remember_e->v); - - /* Advance the iterator, correcting the parent */ - remember_parent = itr->parent; - ret = arg_hashtable_itr_advance(itr); - if (itr->parent == remember_e) { - itr->parent = remember_parent; - } - xfree(remember_e); - return ret; -} - -int arg_hashtable_itr_search(arg_hashtable_itr_t* itr, arg_hashtable_t* h, void* k) { - struct arg_hashtable_entry* e; - struct arg_hashtable_entry* parent; - unsigned int hashvalue; - unsigned int index; - - hashvalue = enhanced_hash(h, k); - index = index_for(h->tablelength, hashvalue); - - e = h->table[index]; - parent = NULL; - while (e != NULL) { - /* Check hash value to short circuit heavier comparison */ - if ((hashvalue == e->h) && (h->eqfn(k, e->k))) { - itr->index = index; - itr->e = e; - itr->parent = parent; - itr->h = h; - return -1; - } - parent = e; - e = e->next; - } - return 0; -} - -int arg_hashtable_change(arg_hashtable_t* h, void* k, void* v) { - struct arg_hashtable_entry* e; - unsigned int hashvalue; - unsigned int index; - - hashvalue = enhanced_hash(h, k); - index = index_for(h->tablelength, hashvalue); - e = h->table[index]; - while (e != NULL) { - /* Check hash value to short circuit heavier comparison */ - if ((hashvalue == e->h) && (h->eqfn(k, e->k))) { - xfree(e->v); - e->v = v; - return -1; - } - e = e->next; - } - return 0; -} -/******************************************************************************* - * arg_dstr: Implements the dynamic string utilities - * - * This file is part of the argtable3 library. - * - * Copyright (C) 2013-2019 Tom G. Huang - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include "argtable3.h" - -#ifndef ARG_AMALGAMATION -#include "argtable3_private.h" -#endif - -#include -#include -#include - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable : 4996) -#endif - -#define START_VSNBUFF 16 - -/* - * This dynamic string module is adapted from TclResult.c in the Tcl library. - * Here is the copyright notice from the library: - * - * This software is copyrighted by the Regents of the University of - * California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState - * Corporation and other parties. The following terms apply to all files - * associated with the software unless explicitly disclaimed in - * individual files. - * - * The authors hereby grant permission to use, copy, modify, distribute, - * and license this software and its documentation for any purpose, provided - * that existing copyright notices are retained in all copies and that this - * notice is included verbatim in any distributions. No written agreement, - * license, or royalty fee is required for any of the authorized uses. - * Modifications to this software may be copyrighted by their authors - * and need not follow the licensing terms described here, provided that - * the new terms are clearly indicated on the first page of each file where - * they apply. - * - * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY - * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES - * ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY - * DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE - * IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE - * NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR - * MODIFICATIONS. - * - * GOVERNMENT USE: If you are acquiring this software on behalf of the - * U.S. government, the Government shall have only "Restricted Rights" - * in the software and related documentation as defined in the Federal - * Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you - * are acquiring the software on behalf of the Department of Defense, the - * software shall be classified as "Commercial Computer Software" and the - * Government shall have only "Restricted Rights" as defined in Clause - * 252.227-7014 (b) (3) of DFARs. Notwithstanding the foregoing, the - * authors grant the U.S. Government and others acting in its behalf - * permission to use and distribute the software in accordance with the - * terms specified in this license. - */ - -typedef struct _internal_arg_dstr { - char* data; - arg_dstr_freefn* free_proc; - char sbuf[ARG_DSTR_SIZE + 1]; - char* append_data; - int append_data_size; - int append_used; -} _internal_arg_dstr_t; - -static void setup_append_buf(arg_dstr_t res, int newSpace); - -arg_dstr_t arg_dstr_create(void) { - _internal_arg_dstr_t* h = (_internal_arg_dstr_t*)xmalloc(sizeof(_internal_arg_dstr_t)); - memset(h, 0, sizeof(_internal_arg_dstr_t)); - h->sbuf[0] = 0; - h->data = h->sbuf; - h->free_proc = ARG_DSTR_STATIC; - return h; -} - -void arg_dstr_destroy(arg_dstr_t ds) { - if (ds == NULL) - return; - - arg_dstr_reset(ds); - xfree(ds); - return; -} - -void arg_dstr_set(arg_dstr_t ds, char* str, arg_dstr_freefn* free_proc) { - int length; - register arg_dstr_freefn* old_free_proc = ds->free_proc; - char* old_result = ds->data; - - if (str == NULL) { - ds->sbuf[0] = 0; - ds->data = ds->sbuf; - ds->free_proc = ARG_DSTR_STATIC; - } else if (free_proc == ARG_DSTR_VOLATILE) { - length = (int)strlen(str); - if (length > ARG_DSTR_SIZE) { - ds->data = (char*)xmalloc((unsigned)length + 1); - ds->free_proc = ARG_DSTR_DYNAMIC; - } else { - ds->data = ds->sbuf; - ds->free_proc = ARG_DSTR_STATIC; - } - strcpy(ds->data, str); - } else { - ds->data = str; - ds->free_proc = free_proc; - } - - /* - * If the old result was dynamically-allocated, free it up. Do it here, - * rather than at the beginning, in case the new result value was part of - * the old result value. - */ - - if ((old_free_proc != 0) && (old_result != ds->data)) { - if (old_free_proc == ARG_DSTR_DYNAMIC) { - xfree(old_result); - } else { - (*old_free_proc)(old_result); - } - } - - if ((ds->append_data != NULL) && (ds->append_data_size > 0)) { - xfree(ds->append_data); - ds->append_data = NULL; - ds->append_data_size = 0; - } -} - -char* arg_dstr_cstr(arg_dstr_t ds) /* Interpreter whose result to return. */ -{ - return ds->data; -} - -void arg_dstr_cat(arg_dstr_t ds, const char* str) { - setup_append_buf(ds, (int)strlen(str) + 1); - memcpy(ds->data + strlen(ds->data), str, strlen(str)); -} - -void arg_dstr_catc(arg_dstr_t ds, char c) { - setup_append_buf(ds, 2); - memcpy(ds->data + strlen(ds->data), &c, 1); -} - -/* - * The logic of the `arg_dstr_catf` function is adapted from the `bformat` - * function in The Better String Library by Paul Hsieh. Here is the copyright - * notice from the library: - * - * Copyright (c) 2014, Paul Hsieh - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * * Neither the name of bstrlib nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -void arg_dstr_catf(arg_dstr_t ds, const char* fmt, ...) { - va_list arglist; - char* buff; - int n, r; - size_t slen; - - if (fmt == NULL) - return; - - /* Since the length is not determinable beforehand, a search is - performed using the truncating "vsnprintf" call (to avoid buffer - overflows) on increasing potential sizes for the output result. */ - - if ((n = (int)(2 * strlen(fmt))) < START_VSNBUFF) - n = START_VSNBUFF; - - buff = (char*)xmalloc((size_t)(n + 2)); - memset(buff, 0, (size_t)(n + 2)); - - for (;;) { - va_start(arglist, fmt); - r = vsnprintf(buff, (size_t)(n + 1), fmt, arglist); - va_end(arglist); - - slen = strlen(buff); - if (slen < (size_t)n) - break; - - if (r > n) - n = r; - else - n += n; - - xfree(buff); - buff = (char*)xmalloc((size_t)(n + 2)); - memset(buff, 0, (size_t)(n + 2)); - } - - arg_dstr_cat(ds, buff); - xfree(buff); -} - -static void setup_append_buf(arg_dstr_t ds, int new_space) { - int total_space; - - /* - * Make the append buffer larger, if that's necessary, then copy the - * data into the append buffer and make the append buffer the official - * data. - */ - if (ds->data != ds->append_data) { - /* - * If the buffer is too big, then free it up so we go back to a - * smaller buffer. This avoids tying up memory forever after a large - * operation. - */ - if (ds->append_data_size > 500) { - xfree(ds->append_data); - ds->append_data = NULL; - ds->append_data_size = 0; - } - ds->append_used = (int)strlen(ds->data); - } else if (ds->data[ds->append_used] != 0) { - /* - * Most likely someone has modified a result created by - * arg_dstr_cat et al. so that it has a different size. Just - * recompute the size. - */ - ds->append_used = (int)strlen(ds->data); - } - - total_space = new_space + ds->append_used; - if (total_space >= ds->append_data_size) { - char* newbuf; - - if (total_space < 100) { - total_space = 200; - } else { - total_space *= 2; - } - newbuf = (char*)xmalloc((unsigned)total_space); - memset(newbuf, 0, (size_t)total_space); - strcpy(newbuf, ds->data); - if (ds->append_data != NULL) { - xfree(ds->append_data); - } - ds->append_data = newbuf; - ds->append_data_size = total_space; - } else if (ds->data != ds->append_data) { - strcpy(ds->append_data, ds->data); - } - - arg_dstr_free(ds); - ds->data = ds->append_data; -} - -void arg_dstr_free(arg_dstr_t ds) { - if (ds->free_proc != NULL) { - if (ds->free_proc == ARG_DSTR_DYNAMIC) { - xfree(ds->data); - } else { - (*ds->free_proc)(ds->data); - } - ds->free_proc = NULL; - } -} - -void arg_dstr_reset(arg_dstr_t ds) { - arg_dstr_free(ds); - if ((ds->append_data != NULL) && (ds->append_data_size > 0)) { - xfree(ds->append_data); - ds->append_data = NULL; - ds->append_data_size = 0; - } - - ds->data = ds->sbuf; - ds->sbuf[0] = 0; -} - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif -/* $NetBSD: getopt.h,v 1.4 2000/07/07 10:43:54 ad Exp $ */ -/* $FreeBSD$ */ - -/*- - * SPDX-License-Identifier: BSD-2-Clause-NetBSD - * - * Copyright (c) 2000 The NetBSD Foundation, Inc. - * All rights reserved. - * - * This code is derived from software contributed to The NetBSD Foundation - * by Dieter Baron and Thomas Klausner. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS - * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS - * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#if ARG_REPLACE_GETOPT == 1 - -#ifndef _GETOPT_H_ -#define _GETOPT_H_ - -/* - * GNU-like getopt_long()/getopt_long_only() with 4.4BSD optreset extension. - * getopt() is declared here too for GNU programs. - */ -#define no_argument 0 -#define required_argument 1 -#define optional_argument 2 - -struct option { - /* name of long option */ - const char *name; - /* - * one of no_argument, required_argument, and optional_argument: - * whether option takes an argument - */ - int has_arg; - /* if not NULL, set *flag to val when option found */ - int *flag; - /* if flag not NULL, value to set *flag to; else return value */ - int val; -}; - -#ifdef __cplusplus -extern "C" { -#endif - -int getopt_long(int, char * const *, const char *, - const struct option *, int *); -int getopt_long_only(int, char * const *, const char *, - const struct option *, int *); -#ifndef _GETOPT_DECLARED -#define _GETOPT_DECLARED -int getopt(int, char * const [], const char *); - -extern char *optarg; /* getopt(3) external variables */ -extern int optind, opterr, optopt; -#endif -#ifndef _OPTRESET_DECLARED -#define _OPTRESET_DECLARED -extern int optreset; /* getopt(3) external variable */ -#endif - -#ifdef __cplusplus -} -#endif - -#endif /* !_GETOPT_H_ */ - -#endif /* ARG_REPLACE_GETOPT == 1 */ -/* $OpenBSD: getopt_long.c,v 1.26 2013/06/08 22:47:56 millert Exp $ */ -/* $NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $ */ - -/* - * Copyright (c) 2002 Todd C. Miller - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * Sponsored in part by the Defense Advanced Research Projects - * Agency (DARPA) and Air Force Research Laboratory, Air Force - * Materiel Command, USAF, under agreement number F39502-99-1-0512. - */ -/*- - * Copyright (c) 2000 The NetBSD Foundation, Inc. - * All rights reserved. - * - * This code is derived from software contributed to The NetBSD Foundation - * by Dieter Baron and Thomas Klausner. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS - * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS - * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include "argtable3.h" - -#if ARG_REPLACE_GETOPT == 1 - -#ifndef ARG_AMALGAMATION -#include "arg_getopt.h" -#endif - -#include -#include -#include - -#define GNU_COMPATIBLE /* Be more compatible, configure's use us! */ - -int opterr = 1; /* if error message should be printed */ -int optind = 1; /* index into parent argv vector */ -int optopt = '?'; /* character checked for validity */ -int optreset; /* reset getopt */ -char *optarg; /* argument associated with option */ - -#define PRINT_ERROR ((opterr) && (*options != ':')) - -#define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */ -#define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */ -#define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */ - -/* return values */ -#define BADCH (int)'?' -#define BADARG ((*options == ':') ? (int)':' : (int)'?') -#define INORDER (int)1 - -#define EMSG "" - -#ifdef GNU_COMPATIBLE -#define NO_PREFIX (-1) -#define D_PREFIX 0 -#define DD_PREFIX 1 -#define W_PREFIX 2 -#endif - -static int getopt_internal(int, char * const *, const char *, - const struct option *, int *, int); -static int parse_long_options(char * const *, const char *, - const struct option *, int *, int, int); -static int gcd(int, int); -static void permute_args(int, int, int, char * const *); - -static char *place = EMSG; /* option letter processing */ - -/* XXX: set optreset to 1 rather than these two */ -static int nonopt_start = -1; /* first non option argument (for permute) */ -static int nonopt_end = -1; /* first option after non options (for permute) */ - -/* Error messages */ -static const char recargchar[] = "option requires an argument -- %c"; -static const char illoptchar[] = "illegal option -- %c"; /* From P1003.2 */ -#ifdef GNU_COMPATIBLE -static int dash_prefix = NO_PREFIX; -static const char gnuoptchar[] = "invalid option -- %c"; - -static const char recargstring[] = "option `%s%s' requires an argument"; -static const char ambig[] = "option `%s%.*s' is ambiguous"; -static const char noarg[] = "option `%s%.*s' doesn't allow an argument"; -static const char illoptstring[] = "unrecognized option `%s%s'"; -#else -static const char recargstring[] = "option requires an argument -- %s"; -static const char ambig[] = "ambiguous option -- %.*s"; -static const char noarg[] = "option doesn't take an argument -- %.*s"; -static const char illoptstring[] = "unknown option -- %s"; -#endif - -#ifdef _WIN32 - -/* - * Windows needs warnx(). We change the definition though: - * 1. (another) global is defined, opterrmsg, which holds the error message - * 2. errors are always printed out on stderr w/o the program name - * Note that opterrmsg always gets set no matter what opterr is set to. The - * error message will not be printed if opterr is 0 as usual. - */ - -#include -#include - -#define MAX_OPTERRMSG_SIZE 128 - -extern char opterrmsg[MAX_OPTERRMSG_SIZE]; -char opterrmsg[MAX_OPTERRMSG_SIZE]; /* buffer for the last error message */ - -static void warnx(const char* fmt, ...) { - va_list ap; - va_start(ap, fmt); - - /* - * Make sure opterrmsg is always zero-terminated despite the _vsnprintf() - * implementation specifics and manually suppress the warning. - */ - memset(opterrmsg, 0, sizeof(opterrmsg)); - if (fmt != NULL) -#if (defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || (defined(__STDC_SECURE_LIB__) && defined(__STDC_WANT_SECURE_LIB__)) - _vsnprintf_s(opterrmsg, sizeof(opterrmsg), sizeof(opterrmsg) - 1, fmt, ap); -#else - _vsnprintf(opterrmsg, sizeof(opterrmsg) - 1, fmt, ap); -#endif - - va_end(ap); - -#ifdef _MSC_VER -#pragma warning(suppress : 6053) -#endif - fprintf(stderr, "%s\n", opterrmsg); -} - -#else -#include -#endif /*_WIN32*/ -/* - * Compute the greatest common divisor of a and b. - */ -static int -gcd(int a, int b) -{ - int c; - - c = a % b; - while (c != 0) { - a = b; - b = c; - c = a % b; - } - - return (b); -} - -/* - * Exchange the block from nonopt_start to nonopt_end with the block - * from nonopt_end to opt_end (keeping the same order of arguments - * in each block). - */ -static void -permute_args(int panonopt_start, int panonopt_end, int opt_end, - char * const *nargv) -{ - int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos; - char *swap; - - /* - * compute lengths of blocks and number and size of cycles - */ - nnonopts = panonopt_end - panonopt_start; - nopts = opt_end - panonopt_end; - ncycle = gcd(nnonopts, nopts); - cyclelen = (opt_end - panonopt_start) / ncycle; - - for (i = 0; i < ncycle; i++) { - cstart = panonopt_end+i; - pos = cstart; - for (j = 0; j < cyclelen; j++) { - if (pos >= panonopt_end) - pos -= nnonopts; - else - pos += nopts; - swap = nargv[pos]; - /* LINTED const cast */ - ((char **) nargv)[pos] = nargv[cstart]; - /* LINTED const cast */ - ((char **)nargv)[cstart] = swap; - } - } -} - -/* - * parse_long_options -- - * Parse long options in argc/argv argument vector. - * Returns -1 if short_too is set and the option does not match long_options. - */ -static int -parse_long_options(char * const *nargv, const char *options, - const struct option *long_options, int *idx, int short_too, int flags) -{ - char *current_argv, *has_equal; -#ifdef GNU_COMPATIBLE - char *current_dash; -#endif - size_t current_argv_len; - int i, match, exact_match, second_partial_match; - - current_argv = place; -#ifdef GNU_COMPATIBLE - switch (dash_prefix) { - case D_PREFIX: - current_dash = "-"; - break; - case DD_PREFIX: - current_dash = "--"; - break; - case W_PREFIX: - current_dash = "-W "; - break; - default: - current_dash = ""; - break; - } -#endif - match = -1; - exact_match = 0; - second_partial_match = 0; - - optind++; - - if ((has_equal = strchr(current_argv, '=')) != NULL) { - /* argument found (--option=arg) */ - current_argv_len = (size_t)(has_equal - current_argv); - has_equal++; - } else - current_argv_len = strlen(current_argv); - - for (i = 0; long_options[i].name; i++) { - /* find matching long option */ - if (strncmp(current_argv, long_options[i].name, - current_argv_len)) - continue; - - if (strlen(long_options[i].name) == current_argv_len) { - /* exact match */ - match = i; - exact_match = 1; - break; - } - /* - * If this is a known short option, don't allow - * a partial match of a single character. - */ - if (short_too && current_argv_len == 1) - continue; - - if (match == -1) /* first partial match */ - match = i; - else if ((flags & FLAG_LONGONLY) || - long_options[i].has_arg != - long_options[match].has_arg || - long_options[i].flag != long_options[match].flag || - long_options[i].val != long_options[match].val) - second_partial_match = 1; - } - if (!exact_match && second_partial_match) { - /* ambiguous abbreviation */ - if (PRINT_ERROR) - warnx(ambig, -#ifdef GNU_COMPATIBLE - current_dash, -#endif - (int)current_argv_len, - current_argv); - optopt = 0; - return (BADCH); - } - if (match != -1) { /* option found */ - if (long_options[match].has_arg == no_argument - && has_equal) { - if (PRINT_ERROR) - warnx(noarg, -#ifdef GNU_COMPATIBLE - current_dash, -#endif - (int)current_argv_len, - current_argv); - /* - * XXX: GNU sets optopt to val regardless of flag - */ - if (long_options[match].flag == NULL) - optopt = long_options[match].val; - else - optopt = 0; -#ifdef GNU_COMPATIBLE - return (BADCH); -#else - return (BADARG); -#endif - } - if (long_options[match].has_arg == required_argument || - long_options[match].has_arg == optional_argument) { - if (has_equal) - optarg = has_equal; - else if (long_options[match].has_arg == - required_argument) { - /* - * optional argument doesn't use next nargv - */ - optarg = nargv[optind++]; - } - } - if ((long_options[match].has_arg == required_argument) - && (optarg == NULL)) { - /* - * Missing argument; leading ':' indicates no error - * should be generated. - */ - if (PRINT_ERROR) - warnx(recargstring, -#ifdef GNU_COMPATIBLE - current_dash, -#endif - current_argv); - /* - * XXX: GNU sets optopt to val regardless of flag - */ - if (long_options[match].flag == NULL) - optopt = long_options[match].val; - else - optopt = 0; - --optind; - return (BADARG); - } - } else { /* unknown option */ - if (short_too) { - --optind; - return (-1); - } - if (PRINT_ERROR) - warnx(illoptstring, -#ifdef GNU_COMPATIBLE - current_dash, -#endif - current_argv); - optopt = 0; - return (BADCH); - } - if (idx) - *idx = match; - if (long_options[match].flag) { - *long_options[match].flag = long_options[match].val; - return (0); - } else - return (long_options[match].val); -} - -/* - * getopt_internal -- - * Parse argc/argv argument vector. Called by user level routines. - */ -static int -getopt_internal(int nargc, char * const *nargv, const char *options, - const struct option *long_options, int *idx, int flags) -{ - char *oli; /* option letter list index */ - int optchar, short_too; - static int posixly_correct = -1; - - if (options == NULL) - return (-1); - - /* - * XXX Some GNU programs (like cvs) set optind to 0 instead of - * XXX using optreset. Work around this braindamage. - */ - if (optind == 0) - optind = optreset = 1; - - /* - * Disable GNU extensions if POSIXLY_CORRECT is set or options - * string begins with a '+'. - */ - if (posixly_correct == -1 || optreset) { -#if defined(_WIN32) && ((defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || (defined(__STDC_SECURE_LIB__) && defined(__STDC_WANT_SECURE_LIB__))) - size_t requiredSize; - getenv_s(&requiredSize, NULL, 0, "POSIXLY_CORRECT"); - posixly_correct = requiredSize != 0; -#else - posixly_correct = (getenv("POSIXLY_CORRECT") != NULL); -#endif - } - - if (*options == '-') - flags |= FLAG_ALLARGS; - else if (posixly_correct || *options == '+') - flags &= ~FLAG_PERMUTE; - if (*options == '+' || *options == '-') - options++; - - optarg = NULL; - if (optreset) - nonopt_start = nonopt_end = -1; -start: - if (optreset || !*place) { /* update scanning pointer */ - optreset = 0; - if (optind >= nargc) { /* end of argument vector */ - place = EMSG; - if (nonopt_end != -1) { - /* do permutation, if we have to */ - permute_args(nonopt_start, nonopt_end, - optind, nargv); - optind -= nonopt_end - nonopt_start; - } - else if (nonopt_start != -1) { - /* - * If we skipped non-options, set optind - * to the first of them. - */ - optind = nonopt_start; - } - nonopt_start = nonopt_end = -1; - return (-1); - } - if (*(place = nargv[optind]) != '-' || -#ifdef GNU_COMPATIBLE - place[1] == '\0') { -#else - (place[1] == '\0' && strchr(options, '-') == NULL)) { -#endif - place = EMSG; /* found non-option */ - if (flags & FLAG_ALLARGS) { - /* - * GNU extension: - * return non-option as argument to option 1 - */ - optarg = nargv[optind++]; - return (INORDER); - } - if (!(flags & FLAG_PERMUTE)) { - /* - * If no permutation wanted, stop parsing - * at first non-option. - */ - return (-1); - } - /* do permutation */ - if (nonopt_start == -1) - nonopt_start = optind; - else if (nonopt_end != -1) { - permute_args(nonopt_start, nonopt_end, - optind, nargv); - nonopt_start = optind - - (nonopt_end - nonopt_start); - nonopt_end = -1; - } - optind++; - /* process next argument */ - goto start; - } - if (nonopt_start != -1 && nonopt_end == -1) - nonopt_end = optind; - - /* - * If we have "-" do nothing, if "--" we are done. - */ - if (place[1] != '\0' && *++place == '-' && place[1] == '\0') { - optind++; - place = EMSG; - /* - * We found an option (--), so if we skipped - * non-options, we have to permute. - */ - if (nonopt_end != -1) { - permute_args(nonopt_start, nonopt_end, - optind, nargv); - optind -= nonopt_end - nonopt_start; - } - nonopt_start = nonopt_end = -1; - return (-1); - } - } - - /* - * Check long options if: - * 1) we were passed some - * 2) the arg is not just "-" - * 3) either the arg starts with -- we are getopt_long_only() - */ - if (long_options != NULL && place != nargv[optind] && - (*place == '-' || (flags & FLAG_LONGONLY))) { - short_too = 0; -#ifdef GNU_COMPATIBLE - dash_prefix = D_PREFIX; -#endif - if (*place == '-') { - place++; /* --foo long option */ - if (*place == '\0') - return (BADARG); /* malformed option */ -#ifdef GNU_COMPATIBLE - dash_prefix = DD_PREFIX; -#endif - } else if (*place != ':' && strchr(options, *place) != NULL) - short_too = 1; /* could be short option too */ - - optchar = parse_long_options(nargv, options, long_options, - idx, short_too, flags); - if (optchar != -1) { - place = EMSG; - return (optchar); - } - } - - if ((optchar = (int)*place++) == (int)':' || - (optchar == (int)'-' && *place != '\0') || - (oli = strchr(options, optchar)) == NULL) { - /* - * If the user specified "-" and '-' isn't listed in - * options, return -1 (non-option) as per POSIX. - * Otherwise, it is an unknown option character (or ':'). - */ - if (optchar == (int)'-' && *place == '\0') - return (-1); - if (!*place) - ++optind; -#ifdef GNU_COMPATIBLE - if (PRINT_ERROR) - warnx(posixly_correct ? illoptchar : gnuoptchar, - optchar); -#else - if (PRINT_ERROR) - warnx(illoptchar, optchar); -#endif - optopt = optchar; - return (BADCH); - } - if (long_options != NULL && optchar == 'W' && oli[1] == ';') { - /* -W long-option */ - if (*place) /* no space */ - /* NOTHING */; - else if (++optind >= nargc) { /* no arg */ - place = EMSG; - if (PRINT_ERROR) - warnx(recargchar, optchar); - optopt = optchar; - return (BADARG); - } else /* white space */ - place = nargv[optind]; -#ifdef GNU_COMPATIBLE - dash_prefix = W_PREFIX; -#endif - optchar = parse_long_options(nargv, options, long_options, - idx, 0, flags); - place = EMSG; - return (optchar); - } - if (*++oli != ':') { /* doesn't take argument */ - if (!*place) - ++optind; - } else { /* takes (optional) argument */ - optarg = NULL; - if (*place) /* no white space */ - optarg = place; - else if (oli[1] != ':') { /* arg not optional */ - if (++optind >= nargc) { /* no arg */ - place = EMSG; - if (PRINT_ERROR) - warnx(recargchar, optchar); - optopt = optchar; - return (BADARG); - } else - optarg = nargv[optind]; - } - place = EMSG; - ++optind; - } - /* dump back option letter */ - return (optchar); -} - -/* - * getopt -- - * Parse argc/argv argument vector. - * - * [eventually this will replace the BSD getopt] - */ -int -getopt(int nargc, char * const *nargv, const char *options) -{ - - /* - * We don't pass FLAG_PERMUTE to getopt_internal() since - * the BSD getopt(3) (unlike GNU) has never done this. - * - * Furthermore, since many privileged programs call getopt() - * before dropping privileges it makes sense to keep things - * as simple (and bug-free) as possible. - */ - return (getopt_internal(nargc, nargv, options, NULL, NULL, 0)); -} - -/* - * getopt_long -- - * Parse argc/argv argument vector. - */ -int -getopt_long(int nargc, char * const *nargv, const char *options, - const struct option *long_options, int *idx) -{ - - return (getopt_internal(nargc, nargv, options, long_options, idx, - FLAG_PERMUTE)); -} - -/* - * getopt_long_only -- - * Parse argc/argv argument vector. - */ -int -getopt_long_only(int nargc, char * const *nargv, const char *options, - const struct option *long_options, int *idx) -{ - - return (getopt_internal(nargc, nargv, options, long_options, idx, - FLAG_PERMUTE|FLAG_LONGONLY)); -} - -#endif /* ARG_REPLACE_GETOPT == 1 */ -/******************************************************************************* - * arg_date: Implements the date command-line option - * - * This file is part of the argtable3 library. - * - * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include "argtable3.h" - -#ifndef ARG_AMALGAMATION -#include "argtable3_private.h" -#endif - -#include -#include - -char* arg_strptime(const char* buf, const char* fmt, struct tm* tm); - -static void arg_date_resetfn(struct arg_date* parent) { - ARG_TRACE(("%s:resetfn(%p)\n", __FILE__, parent)); - parent->count = 0; -} - -static int arg_date_scanfn(struct arg_date* parent, const char* argval) { - int errorcode = 0; - - if (parent->count == parent->hdr.maxcount) { - errorcode = ARG_ERR_MAXCOUNT; - } else if (!argval) { - /* no argument value was given, leave parent->tmval[] unaltered but still count it */ - parent->count++; - } else { - const char* pend; - struct tm tm = parent->tmval[parent->count]; - - /* parse the given argument value, store result in parent->tmval[] */ - pend = arg_strptime(argval, parent->format, &tm); - if (pend && pend[0] == '\0') - parent->tmval[parent->count++] = tm; - else - errorcode = ARG_ERR_BADDATE; - } - - ARG_TRACE(("%s:scanfn(%p) returns %d\n", __FILE__, parent, errorcode)); - return errorcode; -} - -static int arg_date_checkfn(struct arg_date* parent) { - int errorcode = (parent->count < parent->hdr.mincount) ? ARG_ERR_MINCOUNT : 0; - - ARG_TRACE(("%s:checkfn(%p) returns %d\n", __FILE__, parent, errorcode)); - return errorcode; -} - -static void arg_date_errorfn(struct arg_date* parent, arg_dstr_t ds, int errorcode, const char* argval, const char* progname) { - const char* shortopts = parent->hdr.shortopts; - const char* longopts = parent->hdr.longopts; - const char* datatype = parent->hdr.datatype; - - /* make argval NULL safe */ - argval = argval ? argval : ""; - - arg_dstr_catf(ds, "%s: ", progname); - switch (errorcode) { - case ARG_ERR_MINCOUNT: - arg_dstr_cat(ds, "missing option "); - arg_print_option_ds(ds, shortopts, longopts, datatype, "\n"); - break; - - case ARG_ERR_MAXCOUNT: - arg_dstr_cat(ds, "excess option "); - arg_print_option_ds(ds, shortopts, longopts, argval, "\n"); - break; - - case ARG_ERR_BADDATE: { - struct tm tm; - char buff[200]; - - arg_dstr_catf(ds, "illegal timestamp format \"%s\"\n", argval); - memset(&tm, 0, sizeof(tm)); - arg_strptime("1999-12-31 23:59:59", "%F %H:%M:%S", &tm); - strftime(buff, sizeof(buff), parent->format, &tm); - arg_dstr_catf(ds, "correct format is \"%s\"\n", buff); - break; - } - } -} - -struct arg_date* arg_date0(const char* shortopts, const char* longopts, const char* format, const char* datatype, const char* glossary) { - return arg_daten(shortopts, longopts, format, datatype, 0, 1, glossary); -} - -struct arg_date* arg_date1(const char* shortopts, const char* longopts, const char* format, const char* datatype, const char* glossary) { - return arg_daten(shortopts, longopts, format, datatype, 1, 1, glossary); -} - -struct arg_date* -arg_daten(const char* shortopts, const char* longopts, const char* format, const char* datatype, int mincount, int maxcount, const char* glossary) { - size_t nbytes; - struct arg_date* result; - - /* foolproof things by ensuring maxcount is not less than mincount */ - maxcount = (maxcount < mincount) ? mincount : maxcount; - - /* default time format is the national date format for the locale */ - if (!format) - format = "%x"; - - nbytes = sizeof(struct arg_date) /* storage for struct arg_date */ - + (size_t)maxcount * sizeof(struct tm); /* storage for tmval[maxcount] array */ - - /* allocate storage for the arg_date struct + tmval[] array. */ - /* we use calloc because we want the tmval[] array zero filled. */ - result = (struct arg_date*)xcalloc(1, nbytes); - - /* init the arg_hdr struct */ - result->hdr.flag = ARG_HASVALUE; - result->hdr.shortopts = shortopts; - result->hdr.longopts = longopts; - result->hdr.datatype = datatype ? datatype : format; - result->hdr.glossary = glossary; - result->hdr.mincount = mincount; - result->hdr.maxcount = maxcount; - result->hdr.parent = result; - result->hdr.resetfn = (arg_resetfn*)arg_date_resetfn; - result->hdr.scanfn = (arg_scanfn*)arg_date_scanfn; - result->hdr.checkfn = (arg_checkfn*)arg_date_checkfn; - result->hdr.errorfn = (arg_errorfn*)arg_date_errorfn; - - /* store the tmval[maxcount] array immediately after the arg_date struct */ - result->tmval = (struct tm*)(result + 1); - - /* init the remaining arg_date member variables */ - result->count = 0; - result->format = format; - - ARG_TRACE(("arg_daten() returns %p\n", result)); - return result; -} - -/*- - * Copyright (c) 1997, 1998, 2005, 2008 The NetBSD Foundation, Inc. - * All rights reserved. - * - * This code was contributed to The NetBSD Foundation by Klaus Klein. - * Heavily optimised by David Laight - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS - * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS - * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include - -/* - * We do not implement alternate representations. However, we always - * check whether a given modifier is allowed for a certain conversion. - */ -#define ALT_E 0x01 -#define ALT_O 0x02 -#define LEGAL_ALT(x) \ - { \ - if (alt_format & ~(x)) \ - return (0); \ - } -#define TM_YEAR_BASE (1900) - -static int conv_num(const char**, int*, int, int); - -static const char* day[7] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; - -static const char* abday[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; - -static const char* mon[12] = {"January", "February", "March", "April", "May", "June", - "July", "August", "September", "October", "November", "December"}; - -static const char* abmon[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; - -static const char* am_pm[2] = {"AM", "PM"}; - -static int arg_strcasecmp(const char* s1, const char* s2) { - const unsigned char* us1 = (const unsigned char*)s1; - const unsigned char* us2 = (const unsigned char*)s2; - while (tolower(*us1) == tolower(*us2++)) - if (*us1++ == '\0') - return 0; - - return tolower(*us1) - tolower(*--us2); -} - -static int arg_strncasecmp(const char* s1, const char* s2, size_t n) { - if (n != 0) { - const unsigned char* us1 = (const unsigned char*)s1; - const unsigned char* us2 = (const unsigned char*)s2; - do { - if (tolower(*us1) != tolower(*us2++)) - return tolower(*us1) - tolower(*--us2); - - if (*us1++ == '\0') - break; - } while (--n != 0); - } - - return 0; -} - -char* arg_strptime(const char* buf, const char* fmt, struct tm* tm) { - char c; - const char* bp; - size_t len = 0; - int alt_format, i, split_year = 0; - - bp = buf; - - while ((c = *fmt) != '\0') { - /* Clear `alternate' modifier prior to new conversion. */ - alt_format = 0; - - /* Eat up white-space. */ - if (isspace(c)) { - while (isspace((int)(*bp))) - bp++; - - fmt++; - continue; - } - - if ((c = *fmt++) != '%') - goto literal; - - again: - switch (c = *fmt++) { - case '%': /* "%%" is converted to "%". */ - literal: - if (c != *bp++) - return (0); - break; - - /* - * "Alternative" modifiers. Just set the appropriate flag - * and start over again. - */ - case 'E': /* "%E?" alternative conversion modifier. */ - LEGAL_ALT(0); - alt_format |= ALT_E; - goto again; - - case 'O': /* "%O?" alternative conversion modifier. */ - LEGAL_ALT(0); - alt_format |= ALT_O; - goto again; - - /* - * "Complex" conversion rules, implemented through recursion. - */ - case 'c': /* Date and time, using the locale's format. */ - LEGAL_ALT(ALT_E); - bp = arg_strptime(bp, "%x %X", tm); - if (!bp) - return (0); - break; - - case 'D': /* The date as "%m/%d/%y". */ - LEGAL_ALT(0); - bp = arg_strptime(bp, "%m/%d/%y", tm); - if (!bp) - return (0); - break; - - case 'R': /* The time as "%H:%M". */ - LEGAL_ALT(0); - bp = arg_strptime(bp, "%H:%M", tm); - if (!bp) - return (0); - break; - - case 'r': /* The time in 12-hour clock representation. */ - LEGAL_ALT(0); - bp = arg_strptime(bp, "%I:%M:%S %p", tm); - if (!bp) - return (0); - break; - - case 'T': /* The time as "%H:%M:%S". */ - LEGAL_ALT(0); - bp = arg_strptime(bp, "%H:%M:%S", tm); - if (!bp) - return (0); - break; - - case 'X': /* The time, using the locale's format. */ - LEGAL_ALT(ALT_E); - bp = arg_strptime(bp, "%H:%M:%S", tm); - if (!bp) - return (0); - break; - - case 'x': /* The date, using the locale's format. */ - LEGAL_ALT(ALT_E); - bp = arg_strptime(bp, "%m/%d/%y", tm); - if (!bp) - return (0); - break; - - /* - * "Elementary" conversion rules. - */ - case 'A': /* The day of week, using the locale's form. */ - case 'a': - LEGAL_ALT(0); - for (i = 0; i < 7; i++) { - /* Full name. */ - len = strlen(day[i]); - if (arg_strncasecmp(day[i], bp, len) == 0) - break; - - /* Abbreviated name. */ - len = strlen(abday[i]); - if (arg_strncasecmp(abday[i], bp, len) == 0) - break; - } - - /* Nothing matched. */ - if (i == 7) - return (0); - - tm->tm_wday = i; - bp += len; - break; - - case 'B': /* The month, using the locale's form. */ - case 'b': - case 'h': - LEGAL_ALT(0); - for (i = 0; i < 12; i++) { - /* Full name. */ - len = strlen(mon[i]); - if (arg_strncasecmp(mon[i], bp, len) == 0) - break; - - /* Abbreviated name. */ - len = strlen(abmon[i]); - if (arg_strncasecmp(abmon[i], bp, len) == 0) - break; - } - - /* Nothing matched. */ - if (i == 12) - return (0); - - tm->tm_mon = i; - bp += len; - break; - - case 'C': /* The century number. */ - LEGAL_ALT(ALT_E); - if (!(conv_num(&bp, &i, 0, 99))) - return (0); - - if (split_year) { - tm->tm_year = (tm->tm_year % 100) + (i * 100); - } else { - tm->tm_year = i * 100; - split_year = 1; - } - break; - - case 'd': /* The day of month. */ - case 'e': - LEGAL_ALT(ALT_O); - if (!(conv_num(&bp, &tm->tm_mday, 1, 31))) - return (0); - break; - - case 'k': /* The hour (24-hour clock representation). */ - LEGAL_ALT(0); - /* FALLTHROUGH */ - case 'H': - LEGAL_ALT(ALT_O); - if (!(conv_num(&bp, &tm->tm_hour, 0, 23))) - return (0); - break; - - case 'l': /* The hour (12-hour clock representation). */ - LEGAL_ALT(0); - /* FALLTHROUGH */ - case 'I': - LEGAL_ALT(ALT_O); - if (!(conv_num(&bp, &tm->tm_hour, 1, 12))) - return (0); - if (tm->tm_hour == 12) - tm->tm_hour = 0; - break; - - case 'j': /* The day of year. */ - LEGAL_ALT(0); - if (!(conv_num(&bp, &i, 1, 366))) - return (0); - tm->tm_yday = i - 1; - break; - - case 'M': /* The minute. */ - LEGAL_ALT(ALT_O); - if (!(conv_num(&bp, &tm->tm_min, 0, 59))) - return (0); - break; - - case 'm': /* The month. */ - LEGAL_ALT(ALT_O); - if (!(conv_num(&bp, &i, 1, 12))) - return (0); - tm->tm_mon = i - 1; - break; - - case 'p': /* The locale's equivalent of AM/PM. */ - LEGAL_ALT(0); - /* AM? */ - if (arg_strcasecmp(am_pm[0], bp) == 0) { - if (tm->tm_hour > 11) - return (0); - - bp += strlen(am_pm[0]); - break; - } - /* PM? */ - else if (arg_strcasecmp(am_pm[1], bp) == 0) { - if (tm->tm_hour > 11) - return (0); - - tm->tm_hour += 12; - bp += strlen(am_pm[1]); - break; - } - - /* Nothing matched. */ - return (0); - - case 'S': /* The seconds. */ - LEGAL_ALT(ALT_O); - if (!(conv_num(&bp, &tm->tm_sec, 0, 61))) - return (0); - break; - - case 'U': /* The week of year, beginning on sunday. */ - case 'W': /* The week of year, beginning on monday. */ - LEGAL_ALT(ALT_O); - /* - * XXX This is bogus, as we can not assume any valid - * information present in the tm structure at this - * point to calculate a real value, so just check the - * range for now. - */ - if (!(conv_num(&bp, &i, 0, 53))) - return (0); - break; - - case 'w': /* The day of week, beginning on sunday. */ - LEGAL_ALT(ALT_O); - if (!(conv_num(&bp, &tm->tm_wday, 0, 6))) - return (0); - break; - - case 'Y': /* The year. */ - LEGAL_ALT(ALT_E); - if (!(conv_num(&bp, &i, 0, 9999))) - return (0); - - tm->tm_year = i - TM_YEAR_BASE; - break; - - case 'y': /* The year within 100 years of the epoch. */ - LEGAL_ALT(ALT_E | ALT_O); - if (!(conv_num(&bp, &i, 0, 99))) - return (0); - - if (split_year) { - tm->tm_year = ((tm->tm_year / 100) * 100) + i; - break; - } - split_year = 1; - if (i <= 68) - tm->tm_year = i + 2000 - TM_YEAR_BASE; - else - tm->tm_year = i + 1900 - TM_YEAR_BASE; - break; - - /* - * Miscellaneous conversions. - */ - case 'n': /* Any kind of white-space. */ - case 't': - LEGAL_ALT(0); - while (isspace((int)(*bp))) - bp++; - break; - - default: /* Unknown/unsupported conversion. */ - return (0); - } - } - - /* LINTED functional specification */ - return ((char*)bp); -} - -static int conv_num(const char** buf, int* dest, int llim, int ulim) { - int result = 0; - - /* The limit also determines the number of valid digits. */ - int rulim = ulim; - - if (**buf < '0' || **buf > '9') - return (0); - - do { - result *= 10; - result += *(*buf)++ - '0'; - rulim /= 10; - } while ((result * 10 <= ulim) && rulim && **buf >= '0' && **buf <= '9'); - - if (result < llim || result > ulim) - return (0); - - *dest = result; - return (1); -} -/******************************************************************************* - * arg_dbl: Implements the double command-line option - * - * This file is part of the argtable3 library. - * - * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include "argtable3.h" - -#ifndef ARG_AMALGAMATION -#include "argtable3_private.h" -#endif - -#include - -static void arg_dbl_resetfn(struct arg_dbl* parent) { - ARG_TRACE(("%s:resetfn(%p)\n", __FILE__, parent)); - parent->count = 0; -} - -static int arg_dbl_scanfn(struct arg_dbl* parent, const char* argval) { - int errorcode = 0; - - if (parent->count == parent->hdr.maxcount) { - /* maximum number of arguments exceeded */ - errorcode = ARG_ERR_MAXCOUNT; - } else if (!argval) { - /* a valid argument with no argument value was given. */ - /* This happens when an optional argument value was invoked. */ - /* leave parent argument value unaltered but still count the argument. */ - parent->count++; - } else { - double val; - char* end; - - /* extract double from argval into val */ - val = strtod(argval, &end); - - /* if success then store result in parent->dval[] array otherwise return error*/ - if (*end == 0) - parent->dval[parent->count++] = val; - else - errorcode = ARG_ERR_BADDOUBLE; - } - - ARG_TRACE(("%s:scanfn(%p) returns %d\n", __FILE__, parent, errorcode)); - return errorcode; -} - -static int arg_dbl_checkfn(struct arg_dbl* parent) { - int errorcode = (parent->count < parent->hdr.mincount) ? ARG_ERR_MINCOUNT : 0; - - ARG_TRACE(("%s:checkfn(%p) returns %d\n", __FILE__, parent, errorcode)); - return errorcode; -} - -static void arg_dbl_errorfn(struct arg_dbl* parent, arg_dstr_t ds, int errorcode, const char* argval, const char* progname) { - const char* shortopts = parent->hdr.shortopts; - const char* longopts = parent->hdr.longopts; - const char* datatype = parent->hdr.datatype; - - /* make argval NULL safe */ - argval = argval ? argval : ""; - - arg_dstr_catf(ds, "%s: ", progname); - switch (errorcode) { - case ARG_ERR_MINCOUNT: - arg_dstr_cat(ds, "missing option "); - arg_print_option_ds(ds, shortopts, longopts, datatype, "\n"); - break; - - case ARG_ERR_MAXCOUNT: - arg_dstr_cat(ds, "excess option "); - arg_print_option_ds(ds, shortopts, longopts, argval, "\n"); - break; - - case ARG_ERR_BADDOUBLE: - arg_dstr_catf(ds, "invalid argument \"%s\" to option ", argval); - arg_print_option_ds(ds, shortopts, longopts, datatype, "\n"); - break; - } -} - -struct arg_dbl* arg_dbl0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary) { - return arg_dbln(shortopts, longopts, datatype, 0, 1, glossary); -} - -struct arg_dbl* arg_dbl1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary) { - return arg_dbln(shortopts, longopts, datatype, 1, 1, glossary); -} - -struct arg_dbl* arg_dbln(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary) { - size_t nbytes; - struct arg_dbl* result; - size_t addr; - size_t rem; - - /* foolproof things by ensuring maxcount is not less than mincount */ - maxcount = (maxcount < mincount) ? mincount : maxcount; - - nbytes = sizeof(struct arg_dbl) /* storage for struct arg_dbl */ - + (size_t)(maxcount + 1) * sizeof(double); /* storage for dval[maxcount] array plus one extra for padding to memory boundary */ - - result = (struct arg_dbl*)xmalloc(nbytes); - - /* init the arg_hdr struct */ - result->hdr.flag = ARG_HASVALUE; - result->hdr.shortopts = shortopts; - result->hdr.longopts = longopts; - result->hdr.datatype = datatype ? datatype : ""; - result->hdr.glossary = glossary; - result->hdr.mincount = mincount; - result->hdr.maxcount = maxcount; - result->hdr.parent = result; - result->hdr.resetfn = (arg_resetfn*)arg_dbl_resetfn; - result->hdr.scanfn = (arg_scanfn*)arg_dbl_scanfn; - result->hdr.checkfn = (arg_checkfn*)arg_dbl_checkfn; - result->hdr.errorfn = (arg_errorfn*)arg_dbl_errorfn; - - /* Store the dval[maxcount] array on the first double boundary that - * immediately follows the arg_dbl struct. We do the memory alignment - * purely for SPARC and Motorola systems. They require floats and - * doubles to be aligned on natural boundaries. - */ - addr = (size_t)(result + 1); - rem = addr % sizeof(double); - result->dval = (double*)(addr + sizeof(double) - rem); - ARG_TRACE(("addr=%p, dval=%p, sizeof(double)=%d rem=%d\n", addr, result->dval, (int)sizeof(double), (int)rem)); - - result->count = 0; - - ARG_TRACE(("arg_dbln() returns %p\n", result)); - return result; -} -/******************************************************************************* - * arg_end: Implements the error handling utilities - * - * This file is part of the argtable3 library. - * - * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include "argtable3.h" - -#ifndef ARG_AMALGAMATION -#include "argtable3_private.h" -#endif - -#include - -static void arg_end_resetfn(struct arg_end* parent) { - ARG_TRACE(("%s:resetfn(%p)\n", __FILE__, parent)); - parent->count = 0; -} - -static void arg_end_errorfn(void* parent, arg_dstr_t ds, int error, const char* argval, const char* progname) { - /* suppress unreferenced formal parameter warning */ - (void)parent; - - progname = progname ? progname : ""; - argval = argval ? argval : ""; - - arg_dstr_catf(ds, "%s: ", progname); - switch (error) { - case ARG_ELIMIT: - arg_dstr_cat(ds, "too many errors to display"); - break; - case ARG_EMALLOC: - arg_dstr_cat(ds, "insufficient memory"); - break; - case ARG_ENOMATCH: - arg_dstr_catf(ds, "unexpected argument \"%s\"", argval); - break; - case ARG_EMISSARG: - arg_dstr_catf(ds, "option \"%s\" requires an argument", argval); - break; - case ARG_ELONGOPT: - arg_dstr_catf(ds, "invalid option \"%s\"", argval); - break; - default: - arg_dstr_catf(ds, "invalid option \"-%c\"", error); - break; - } - - arg_dstr_cat(ds, "\n"); -} - -struct arg_end* arg_end(int maxcount) { - size_t nbytes; - struct arg_end* result; - - nbytes = sizeof(struct arg_end) + (size_t)maxcount * sizeof(int) /* storage for int error[maxcount] array*/ - + (size_t)maxcount * sizeof(void*) /* storage for void* parent[maxcount] array */ - + (size_t)maxcount * sizeof(char*); /* storage for char* argval[maxcount] array */ - - result = (struct arg_end*)xmalloc(nbytes); - - /* init the arg_hdr struct */ - result->hdr.flag = ARG_TERMINATOR; - result->hdr.shortopts = NULL; - result->hdr.longopts = NULL; - result->hdr.datatype = NULL; - result->hdr.glossary = NULL; - result->hdr.mincount = 1; - result->hdr.maxcount = maxcount; - result->hdr.parent = result; - result->hdr.resetfn = (arg_resetfn*)arg_end_resetfn; - result->hdr.scanfn = NULL; - result->hdr.checkfn = NULL; - result->hdr.errorfn = (arg_errorfn*)arg_end_errorfn; - - /* store error[maxcount] array immediately after struct arg_end */ - result->error = (int*)(result + 1); - - /* store parent[maxcount] array immediately after error[] array */ - result->parent = (void**)(result->error + maxcount); - - /* store argval[maxcount] array immediately after parent[] array */ - result->argval = (const char**)(result->parent + maxcount); - - ARG_TRACE(("arg_end(%d) returns %p\n", maxcount, result)); - return result; -} - -void arg_print_errors_ds(arg_dstr_t ds, struct arg_end* end, const char* progname) { - int i; - ARG_TRACE(("arg_errors()\n")); - for (i = 0; i < end->count; i++) { - struct arg_hdr* errorparent = (struct arg_hdr*)(end->parent[i]); - if (errorparent->errorfn) - errorparent->errorfn(end->parent[i], ds, end->error[i], end->argval[i], progname); - } -} - -void arg_print_errors(FILE* fp, struct arg_end* end, const char* progname) { - arg_dstr_t ds = arg_dstr_create(); - arg_print_errors_ds(ds, end, progname); - fputs(arg_dstr_cstr(ds), fp); - arg_dstr_destroy(ds); -} -/******************************************************************************* - * arg_file: Implements the file command-line option - * - * This file is part of the argtable3 library. - * - * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include "argtable3.h" - -#ifndef ARG_AMALGAMATION -#include "argtable3_private.h" -#endif - -#include -#include - -#ifdef WIN32 -#define FILESEPARATOR1 '\\' -#define FILESEPARATOR2 '/' -#else -#define FILESEPARATOR1 '/' -#define FILESEPARATOR2 '/' -#endif - -static void arg_file_resetfn(struct arg_file* parent) { - ARG_TRACE(("%s:resetfn(%p)\n", __FILE__, parent)); - parent->count = 0; -} - -/* Returns ptr to the base filename within *filename */ -static const char* arg_basename(const char* filename) { - const char *result = NULL, *result1, *result2; - - /* Find the last occurrence of eother file separator character. */ - /* Two alternative file separator chars are supported as legal */ - /* file separators but not both together in the same filename. */ - result1 = (filename ? strrchr(filename, FILESEPARATOR1) : NULL); - result2 = (filename ? strrchr(filename, FILESEPARATOR2) : NULL); - - if (result2) - result = result2 + 1; /* using FILESEPARATOR2 (the alternative file separator) */ - - if (result1) - result = result1 + 1; /* using FILESEPARATOR1 (the preferred file separator) */ - - if (!result) - result = filename; /* neither file separator was found so basename is the whole filename */ - - /* special cases of "." and ".." are not considered basenames */ - if (result && (strcmp(".", result) == 0 || strcmp("..", result) == 0)) - result = filename + strlen(filename); - - return result; -} - -/* Returns ptr to the file extension within *basename */ -static const char* arg_extension(const char* basename) { - /* find the last occurrence of '.' in basename */ - const char* result = (basename ? strrchr(basename, '.') : NULL); - - /* if no '.' was found then return pointer to end of basename */ - if (basename && !result) - result = basename + strlen(basename); - - /* special case: basenames with a single leading dot (eg ".foo") are not considered as true extensions */ - if (basename && result == basename) - result = basename + strlen(basename); - - /* special case: empty extensions (eg "foo.","foo..") are not considered as true extensions */ - if (basename && result && strlen(result) == 1) - result = basename + strlen(basename); - - return result; -} - -static int arg_file_scanfn(struct arg_file* parent, const char* argval) { - int errorcode = 0; - - if (parent->count == parent->hdr.maxcount) { - /* maximum number of arguments exceeded */ - errorcode = ARG_ERR_MAXCOUNT; - } else if (!argval) { - /* a valid argument with no argument value was given. */ - /* This happens when an optional argument value was invoked. */ - /* leave parent arguiment value unaltered but still count the argument. */ - parent->count++; - } else { - parent->filename[parent->count] = argval; - parent->basename[parent->count] = arg_basename(argval); - parent->extension[parent->count] = - arg_extension(parent->basename[parent->count]); /* only seek extensions within the basename (not the file path)*/ - parent->count++; - } - - ARG_TRACE(("%s4:scanfn(%p) returns %d\n", __FILE__, parent, errorcode)); - return errorcode; -} - -static int arg_file_checkfn(struct arg_file* parent) { - int errorcode = (parent->count < parent->hdr.mincount) ? ARG_ERR_MINCOUNT : 0; - - ARG_TRACE(("%s:checkfn(%p) returns %d\n", __FILE__, parent, errorcode)); - return errorcode; -} - -static void arg_file_errorfn(struct arg_file* parent, arg_dstr_t ds, int errorcode, const char* argval, const char* progname) { - const char* shortopts = parent->hdr.shortopts; - const char* longopts = parent->hdr.longopts; - const char* datatype = parent->hdr.datatype; - - /* make argval NULL safe */ - argval = argval ? argval : ""; - - arg_dstr_catf(ds, "%s: ", progname); - switch (errorcode) { - case ARG_ERR_MINCOUNT: - arg_dstr_cat(ds, "missing option "); - arg_print_option_ds(ds, shortopts, longopts, datatype, "\n"); - break; - - case ARG_ERR_MAXCOUNT: - arg_dstr_cat(ds, "excess option "); - arg_print_option_ds(ds, shortopts, longopts, argval, "\n"); - break; - - default: - arg_dstr_catf(ds, "unknown error at \"%s\"\n", argval); - } -} - -struct arg_file* arg_file0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary) { - return arg_filen(shortopts, longopts, datatype, 0, 1, glossary); -} - -struct arg_file* arg_file1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary) { - return arg_filen(shortopts, longopts, datatype, 1, 1, glossary); -} - -struct arg_file* arg_filen(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary) { - size_t nbytes; - struct arg_file* result; - int i; - - /* foolproof things by ensuring maxcount is not less than mincount */ - maxcount = (maxcount < mincount) ? mincount : maxcount; - - nbytes = sizeof(struct arg_file) /* storage for struct arg_file */ - + sizeof(char*) * (size_t)maxcount /* storage for filename[maxcount] array */ - + sizeof(char*) * (size_t)maxcount /* storage for basename[maxcount] array */ - + sizeof(char*) * (size_t)maxcount; /* storage for extension[maxcount] array */ - - result = (struct arg_file*)xmalloc(nbytes); - - /* init the arg_hdr struct */ - result->hdr.flag = ARG_HASVALUE; - result->hdr.shortopts = shortopts; - result->hdr.longopts = longopts; - result->hdr.glossary = glossary; - result->hdr.datatype = datatype ? datatype : ""; - result->hdr.mincount = mincount; - result->hdr.maxcount = maxcount; - result->hdr.parent = result; - result->hdr.resetfn = (arg_resetfn*)arg_file_resetfn; - result->hdr.scanfn = (arg_scanfn*)arg_file_scanfn; - result->hdr.checkfn = (arg_checkfn*)arg_file_checkfn; - result->hdr.errorfn = (arg_errorfn*)arg_file_errorfn; - - /* store the filename,basename,extension arrays immediately after the arg_file struct */ - result->filename = (const char**)(result + 1); - result->basename = result->filename + maxcount; - result->extension = result->basename + maxcount; - result->count = 0; - - /* foolproof the string pointers by initialising them with empty strings */ - for (i = 0; i < maxcount; i++) { - result->filename[i] = ""; - result->basename[i] = ""; - result->extension[i] = ""; - } - - ARG_TRACE(("arg_filen() returns %p\n", result)); - return result; -} -/******************************************************************************* - * arg_int: Implements the int command-line option - * - * This file is part of the argtable3 library. - * - * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include "argtable3.h" - -#ifndef ARG_AMALGAMATION -#include "argtable3_private.h" -#endif - -#include -#include -#include - -static void arg_int_resetfn(struct arg_int* parent) { - ARG_TRACE(("%s:resetfn(%p)\n", __FILE__, parent)); - parent->count = 0; -} - -/* strtol0x() is like strtol() except that the numeric string is */ -/* expected to be prefixed by "0X" where X is a user supplied char. */ -/* The string may optionally be prefixed by white space and + or - */ -/* as in +0X123 or -0X123. */ -/* Once the prefix has been scanned, the remainder of the numeric */ -/* string is converted using strtol() with the given base. */ -/* eg: to parse hex str="-0X12324", specify X='X' and base=16. */ -/* eg: to parse oct str="+0o12324", specify X='O' and base=8. */ -/* eg: to parse bin str="-0B01010", specify X='B' and base=2. */ -/* Failure of conversion is indicated by result where *endptr==str. */ -static long int strtol0X(const char* str, const char** endptr, char X, int base) { - long int val; /* stores result */ - int s = 1; /* sign is +1 or -1 */ - const char* ptr = str; /* ptr to current position in str */ - - /* skip leading whitespace */ - while (isspace((int)(*ptr))) - ptr++; - /* printf("1) %s\n",ptr); */ - - /* scan optional sign character */ - switch (*ptr) { - case '+': - ptr++; - s = 1; - break; - case '-': - ptr++; - s = -1; - break; - default: - s = 1; - break; - } - /* printf("2) %s\n",ptr); */ - - /* '0X' prefix */ - if ((*ptr++) != '0') { - /* printf("failed to detect '0'\n"); */ - *endptr = str; - return 0; - } - /* printf("3) %s\n",ptr); */ - if (toupper(*ptr++) != toupper(X)) { - /* printf("failed to detect '%c'\n",X); */ - *endptr = str; - return 0; - } - /* printf("4) %s\n",ptr); */ - - /* attempt conversion on remainder of string using strtol() */ - val = strtol(ptr, (char**)endptr, base); - if (*endptr == ptr) { - /* conversion failed */ - *endptr = str; - return 0; - } - - /* success */ - return s * val; -} - -/* Returns 1 if str matches suffix (case insensitive). */ -/* Str may contain trailing whitespace, but nothing else. */ -static int detectsuffix(const char* str, const char* suffix) { - /* scan pairwise through strings until mismatch detected */ - while (toupper(*str) == toupper(*suffix)) { - /* printf("'%c' '%c'\n", *str, *suffix); */ - - /* return 1 (success) if match persists until the string terminator */ - if (*str == '\0') - return 1; - - /* next chars */ - str++; - suffix++; - } - /* printf("'%c' '%c' mismatch\n", *str, *suffix); */ - - /* return 0 (fail) if the matching did not consume the entire suffix */ - if (*suffix != 0) - return 0; /* failed to consume entire suffix */ - - /* skip any remaining whitespace in str */ - while (isspace((int)(*str))) - str++; - - /* return 1 (success) if we have reached end of str else return 0 (fail) */ - return (*str == '\0') ? 1 : 0; -} - -static int arg_int_scanfn(struct arg_int* parent, const char* argval) { - int errorcode = 0; - - if (parent->count == parent->hdr.maxcount) { - /* maximum number of arguments exceeded */ - errorcode = ARG_ERR_MAXCOUNT; - } else if (!argval) { - /* a valid argument with no argument value was given. */ - /* This happens when an optional argument value was invoked. */ - /* leave parent arguiment value unaltered but still count the argument. */ - parent->count++; - } else { - long int val; - const char* end; - - /* attempt to extract hex integer (eg: +0x123) from argval into val conversion */ - val = strtol0X(argval, &end, 'X', 16); - if (end == argval) { - /* hex failed, attempt octal conversion (eg +0o123) */ - val = strtol0X(argval, &end, 'O', 8); - if (end == argval) { - /* octal failed, attempt binary conversion (eg +0B101) */ - val = strtol0X(argval, &end, 'B', 2); - if (end == argval) { - /* binary failed, attempt decimal conversion with no prefix (eg 1234) */ - val = strtol(argval, (char**)&end, 10); - if (end == argval) { - /* all supported number formats failed */ - return ARG_ERR_BADINT; - } - } - } - } - - /* Safety check for integer overflow. WARNING: this check */ - /* achieves nothing on machines where size(int)==size(long). */ - if (val > INT_MAX || val < INT_MIN) - errorcode = ARG_ERR_OVERFLOW; - - /* Detect any suffixes (KB,MB,GB) and multiply argument value appropriately. */ - /* We need to be mindful of integer overflows when using such big numbers. */ - if (detectsuffix(end, "KB")) /* kilobytes */ - { - if (val > (INT_MAX / 1024) || val < (INT_MIN / 1024)) - errorcode = ARG_ERR_OVERFLOW; /* Overflow would occur if we proceed */ - else - val *= 1024; /* 1KB = 1024 */ - } else if (detectsuffix(end, "MB")) /* megabytes */ - { - if (val > (INT_MAX / 1048576) || val < (INT_MIN / 1048576)) - errorcode = ARG_ERR_OVERFLOW; /* Overflow would occur if we proceed */ - else - val *= 1048576; /* 1MB = 1024*1024 */ - } else if (detectsuffix(end, "GB")) /* gigabytes */ - { - if (val > (INT_MAX / 1073741824) || val < (INT_MIN / 1073741824)) - errorcode = ARG_ERR_OVERFLOW; /* Overflow would occur if we proceed */ - else - val *= 1073741824; /* 1GB = 1024*1024*1024 */ - } else if (!detectsuffix(end, "")) - errorcode = ARG_ERR_BADINT; /* invalid suffix detected */ - - /* if success then store result in parent->ival[] array */ - if (errorcode == 0) - parent->ival[parent->count++] = (int)val; - } - - /* printf("%s:scanfn(%p,%p) returns %d\n",__FILE__,parent,argval,errorcode); */ - return errorcode; -} - -static int arg_int_checkfn(struct arg_int* parent) { - int errorcode = (parent->count < parent->hdr.mincount) ? ARG_ERR_MINCOUNT : 0; - /*printf("%s:checkfn(%p) returns %d\n",__FILE__,parent,errorcode);*/ - return errorcode; -} - -static void arg_int_errorfn(struct arg_int* parent, arg_dstr_t ds, int errorcode, const char* argval, const char* progname) { - const char* shortopts = parent->hdr.shortopts; - const char* longopts = parent->hdr.longopts; - const char* datatype = parent->hdr.datatype; - - /* make argval NULL safe */ - argval = argval ? argval : ""; - - arg_dstr_catf(ds, "%s: ", progname); - switch (errorcode) { - case ARG_ERR_MINCOUNT: - arg_dstr_cat(ds, "missing option "); - arg_print_option_ds(ds, shortopts, longopts, datatype, "\n"); - break; - - case ARG_ERR_MAXCOUNT: - arg_dstr_cat(ds, "excess option "); - arg_print_option_ds(ds, shortopts, longopts, argval, "\n"); - break; - - case ARG_ERR_BADINT: - arg_dstr_catf(ds, "invalid argument \"%s\" to option ", argval); - arg_print_option_ds(ds, shortopts, longopts, datatype, "\n"); - break; - - case ARG_ERR_OVERFLOW: - arg_dstr_cat(ds, "integer overflow at option "); - arg_print_option_ds(ds, shortopts, longopts, datatype, " "); - arg_dstr_catf(ds, "(%s is too large)\n", argval); - break; - } -} - -struct arg_int* arg_int0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary) { - return arg_intn(shortopts, longopts, datatype, 0, 1, glossary); -} - -struct arg_int* arg_int1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary) { - return arg_intn(shortopts, longopts, datatype, 1, 1, glossary); -} - -struct arg_int* arg_intn(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary) { - size_t nbytes; - struct arg_int* result; - - /* foolproof things by ensuring maxcount is not less than mincount */ - maxcount = (maxcount < mincount) ? mincount : maxcount; - - nbytes = sizeof(struct arg_int) /* storage for struct arg_int */ - + (size_t)maxcount * sizeof(int); /* storage for ival[maxcount] array */ - - result = (struct arg_int*)xmalloc(nbytes); - - /* init the arg_hdr struct */ - result->hdr.flag = ARG_HASVALUE; - result->hdr.shortopts = shortopts; - result->hdr.longopts = longopts; - result->hdr.datatype = datatype ? datatype : ""; - result->hdr.glossary = glossary; - result->hdr.mincount = mincount; - result->hdr.maxcount = maxcount; - result->hdr.parent = result; - result->hdr.resetfn = (arg_resetfn*)arg_int_resetfn; - result->hdr.scanfn = (arg_scanfn*)arg_int_scanfn; - result->hdr.checkfn = (arg_checkfn*)arg_int_checkfn; - result->hdr.errorfn = (arg_errorfn*)arg_int_errorfn; - - /* store the ival[maxcount] array immediately after the arg_int struct */ - result->ival = (int*)(result + 1); - result->count = 0; - - ARG_TRACE(("arg_intn() returns %p\n", result)); - return result; -} -/******************************************************************************* - * arg_lit: Implements the literature command-line option - * - * This file is part of the argtable3 library. - * - * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include "argtable3.h" - -#ifndef ARG_AMALGAMATION -#include "argtable3_private.h" -#endif - -#include - -static void arg_lit_resetfn(struct arg_lit* parent) { - ARG_TRACE(("%s:resetfn(%p)\n", __FILE__, parent)); - parent->count = 0; -} - -static int arg_lit_scanfn(struct arg_lit* parent, const char* argval) { - int errorcode = 0; - if (parent->count < parent->hdr.maxcount) - parent->count++; - else - errorcode = ARG_ERR_MAXCOUNT; - - ARG_TRACE(("%s:scanfn(%p,%s) returns %d\n", __FILE__, parent, argval, errorcode)); - return errorcode; -} - -static int arg_lit_checkfn(struct arg_lit* parent) { - int errorcode = (parent->count < parent->hdr.mincount) ? ARG_ERR_MINCOUNT : 0; - ARG_TRACE(("%s:checkfn(%p) returns %d\n", __FILE__, parent, errorcode)); - return errorcode; -} - -static void arg_lit_errorfn(struct arg_lit* parent, arg_dstr_t ds, int errorcode, const char* argval, const char* progname) { - const char* shortopts = parent->hdr.shortopts; - const char* longopts = parent->hdr.longopts; - const char* datatype = parent->hdr.datatype; - - switch (errorcode) { - case ARG_ERR_MINCOUNT: - arg_dstr_catf(ds, "%s: missing option ", progname); - arg_print_option_ds(ds, shortopts, longopts, datatype, "\n"); - arg_dstr_cat(ds, "\n"); - break; - - case ARG_ERR_MAXCOUNT: - arg_dstr_catf(ds, "%s: extraneous option ", progname); - arg_print_option_ds(ds, shortopts, longopts, datatype, "\n"); - break; - } - - ARG_TRACE(("%s:errorfn(%p, %p, %d, %s, %s)\n", __FILE__, parent, ds, errorcode, argval, progname)); -} - -struct arg_lit* arg_lit0(const char* shortopts, const char* longopts, const char* glossary) { - return arg_litn(shortopts, longopts, 0, 1, glossary); -} - -struct arg_lit* arg_lit1(const char* shortopts, const char* longopts, const char* glossary) { - return arg_litn(shortopts, longopts, 1, 1, glossary); -} - -struct arg_lit* arg_litn(const char* shortopts, const char* longopts, int mincount, int maxcount, const char* glossary) { - struct arg_lit* result; - - /* foolproof things by ensuring maxcount is not less than mincount */ - maxcount = (maxcount < mincount) ? mincount : maxcount; - - result = (struct arg_lit*)xmalloc(sizeof(struct arg_lit)); - - /* init the arg_hdr struct */ - result->hdr.flag = 0; - result->hdr.shortopts = shortopts; - result->hdr.longopts = longopts; - result->hdr.datatype = NULL; - result->hdr.glossary = glossary; - result->hdr.mincount = mincount; - result->hdr.maxcount = maxcount; - result->hdr.parent = result; - result->hdr.resetfn = (arg_resetfn*)arg_lit_resetfn; - result->hdr.scanfn = (arg_scanfn*)arg_lit_scanfn; - result->hdr.checkfn = (arg_checkfn*)arg_lit_checkfn; - result->hdr.errorfn = (arg_errorfn*)arg_lit_errorfn; - - /* init local variables */ - result->count = 0; - - ARG_TRACE(("arg_litn() returns %p\n", result)); - return result; -} -/******************************************************************************* - * arg_rem: Implements the rem command-line option - * - * This file is part of the argtable3 library. - * - * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include "argtable3.h" - -#ifndef ARG_AMALGAMATION -#include "argtable3_private.h" -#endif - -#include - -struct arg_rem* arg_rem(const char* datatype, const char* glossary) { - struct arg_rem* result = (struct arg_rem*)xmalloc(sizeof(struct arg_rem)); - - result->hdr.flag = 0; - result->hdr.shortopts = NULL; - result->hdr.longopts = NULL; - result->hdr.datatype = datatype; - result->hdr.glossary = glossary; - result->hdr.mincount = 1; - result->hdr.maxcount = 1; - result->hdr.parent = result; - result->hdr.resetfn = NULL; - result->hdr.scanfn = NULL; - result->hdr.checkfn = NULL; - result->hdr.errorfn = NULL; - - ARG_TRACE(("arg_rem() returns %p\n", result)); - return result; -} -/******************************************************************************* - * arg_rex: Implements the regex command-line option - * - * This file is part of the argtable3 library. - * - * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include "argtable3.h" - -#ifndef ARG_AMALGAMATION -#include "argtable3_private.h" -#endif - -#include -#include - -#ifndef _TREX_H_ -#define _TREX_H_ - -/* - * This module uses the T-Rex regular expression library to implement the regex - * logic. Here is the copyright notice of the library: - * - * Copyright (C) 2003-2006 Alberto Demichelis - * - * This software is provided 'as-is', without any express - * or implied warranty. In no event will the authors be held - * liable for any damages arising from the use of this software. - * - * Permission is granted to anyone to use this software for - * any purpose, including commercial applications, and to alter - * it and redistribute it freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; - * you must not claim that you wrote the original software. - * If you use this software in a product, an acknowledgment - * in the product documentation would be appreciated but - * is not required. - * - * 2. Altered source versions must be plainly marked as such, - * and must not be misrepresented as being the original software. - * - * 3. This notice may not be removed or altered from any - * source distribution. - */ - -#ifdef __cplusplus -extern "C" { -#endif - -#define TRexChar char -#define MAX_CHAR 0xFF -#define _TREXC(c) (c) -#define trex_strlen strlen -#define trex_printf printf - -#ifndef TREX_API -#define TREX_API extern -#endif - -#define TRex_True 1 -#define TRex_False 0 - -#define TREX_ICASE ARG_REX_ICASE - -typedef unsigned int TRexBool; -typedef struct TRex TRex; - -typedef struct { - const TRexChar* begin; - int len; -} TRexMatch; - -#if defined(__clang__) -TREX_API TRex* trex_compile(const TRexChar* pattern, const TRexChar** error, int flags) __attribute__((optnone)); -#elif defined(__GNUC__) -TREX_API TRex* trex_compile(const TRexChar* pattern, const TRexChar** error, int flags) __attribute__((optimize(0))); -#else -TREX_API TRex* trex_compile(const TRexChar* pattern, const TRexChar** error, int flags); -#endif -TREX_API void trex_free(TRex* exp); -TREX_API TRexBool trex_match(TRex* exp, const TRexChar* text); -TREX_API TRexBool trex_search(TRex* exp, const TRexChar* text, const TRexChar** out_begin, const TRexChar** out_end); -TREX_API TRexBool -trex_searchrange(TRex* exp, const TRexChar* text_begin, const TRexChar* text_end, const TRexChar** out_begin, const TRexChar** out_end); -TREX_API int trex_getsubexpcount(TRex* exp); -TREX_API TRexBool trex_getsubexp(TRex* exp, int n, TRexMatch* subexp); - -#ifdef __cplusplus -} -#endif - -#endif - -struct privhdr { - const char* pattern; - int flags; -}; - -static void arg_rex_resetfn(struct arg_rex* parent) { - ARG_TRACE(("%s:resetfn(%p)\n", __FILE__, parent)); - parent->count = 0; -} - -static int arg_rex_scanfn(struct arg_rex* parent, const char* argval) { - int errorcode = 0; - const TRexChar* error = NULL; - TRex* rex = NULL; - TRexBool is_match = TRex_False; - - if (parent->count == parent->hdr.maxcount) { - /* maximum number of arguments exceeded */ - errorcode = ARG_ERR_MAXCOUNT; - } else if (!argval) { - /* a valid argument with no argument value was given. */ - /* This happens when an optional argument value was invoked. */ - /* leave parent argument value unaltered but still count the argument. */ - parent->count++; - } else { - struct privhdr* priv = (struct privhdr*)parent->hdr.priv; - - /* test the current argument value for a match with the regular expression */ - /* if a match is detected, record the argument value in the arg_rex struct */ - - rex = trex_compile(priv->pattern, &error, priv->flags); - is_match = trex_match(rex, argval); - if (!is_match) - errorcode = ARG_ERR_REGNOMATCH; - else - parent->sval[parent->count++] = argval; - - trex_free(rex); - } - - ARG_TRACE(("%s:scanfn(%p) returns %d\n", __FILE__, parent, errorcode)); - return errorcode; -} - -static int arg_rex_checkfn(struct arg_rex* parent) { - int errorcode = (parent->count < parent->hdr.mincount) ? ARG_ERR_MINCOUNT : 0; -#if 0 - struct privhdr *priv = (struct privhdr*)parent->hdr.priv; - - /* free the regex "program" we constructed in resetfn */ - regfree(&(priv->regex)); - - /*printf("%s:checkfn(%p) returns %d\n",__FILE__,parent,errorcode);*/ -#endif - return errorcode; -} - -static void arg_rex_errorfn(struct arg_rex* parent, arg_dstr_t ds, int errorcode, const char* argval, const char* progname) { - const char* shortopts = parent->hdr.shortopts; - const char* longopts = parent->hdr.longopts; - const char* datatype = parent->hdr.datatype; - - /* make argval NULL safe */ - argval = argval ? argval : ""; - - arg_dstr_catf(ds, "%s: ", progname); - switch (errorcode) { - case ARG_ERR_MINCOUNT: - arg_dstr_cat(ds, "missing option "); - arg_print_option_ds(ds, shortopts, longopts, datatype, "\n"); - break; - - case ARG_ERR_MAXCOUNT: - arg_dstr_cat(ds, "excess option "); - arg_print_option_ds(ds, shortopts, longopts, argval, "\n"); - break; - - case ARG_ERR_REGNOMATCH: - arg_dstr_cat(ds, "illegal value "); - arg_print_option_ds(ds, shortopts, longopts, argval, "\n"); - break; - - default: { - #if 0 - char errbuff[256]; - regerror(errorcode, NULL, errbuff, sizeof(errbuff)); - printf("%s\n", errbuff); - #endif - } break; - } -} - -struct arg_rex* arg_rex0(const char* shortopts, const char* longopts, const char* pattern, const char* datatype, int flags, const char* glossary) { - return arg_rexn(shortopts, longopts, pattern, datatype, 0, 1, flags, glossary); -} - -struct arg_rex* arg_rex1(const char* shortopts, const char* longopts, const char* pattern, const char* datatype, int flags, const char* glossary) { - return arg_rexn(shortopts, longopts, pattern, datatype, 1, 1, flags, glossary); -} - -struct arg_rex* arg_rexn(const char* shortopts, - const char* longopts, - const char* pattern, - const char* datatype, - int mincount, - int maxcount, - int flags, - const char* glossary) { - size_t nbytes; - struct arg_rex* result; - struct privhdr* priv; - int i; - const TRexChar* error = NULL; - TRex* rex = NULL; - - if (!pattern) { - printf("argtable: ERROR - illegal regular expression pattern \"(NULL)\"\n"); - printf("argtable: Bad argument table.\n"); - return NULL; - } - - /* foolproof things by ensuring maxcount is not less than mincount */ - maxcount = (maxcount < mincount) ? mincount : maxcount; - - nbytes = sizeof(struct arg_rex) /* storage for struct arg_rex */ - + sizeof(struct privhdr) /* storage for private arg_rex data */ - + (size_t)maxcount * sizeof(char*); /* storage for sval[maxcount] array */ - - /* init the arg_hdr struct */ - result = (struct arg_rex*)xmalloc(nbytes); - result->hdr.flag = ARG_HASVALUE; - result->hdr.shortopts = shortopts; - result->hdr.longopts = longopts; - result->hdr.datatype = datatype ? datatype : pattern; - result->hdr.glossary = glossary; - result->hdr.mincount = mincount; - result->hdr.maxcount = maxcount; - result->hdr.parent = result; - result->hdr.resetfn = (arg_resetfn*)arg_rex_resetfn; - result->hdr.scanfn = (arg_scanfn*)arg_rex_scanfn; - result->hdr.checkfn = (arg_checkfn*)arg_rex_checkfn; - result->hdr.errorfn = (arg_errorfn*)arg_rex_errorfn; - - /* store the arg_rex_priv struct immediately after the arg_rex struct */ - result->hdr.priv = result + 1; - priv = (struct privhdr*)(result->hdr.priv); - priv->pattern = pattern; - priv->flags = flags; - - /* store the sval[maxcount] array immediately after the arg_rex_priv struct */ - result->sval = (const char**)(priv + 1); - result->count = 0; - - /* foolproof the string pointers by initializing them to reference empty strings */ - for (i = 0; i < maxcount; i++) - result->sval[i] = ""; - - /* here we construct and destroy a regex representation of the regular - * expression for no other reason than to force any regex errors to be - * trapped now rather than later. If we don't, then errors may go undetected - * until an argument is actually parsed. - */ - - rex = trex_compile(priv->pattern, &error, priv->flags); - if (rex == NULL) { - ARG_LOG(("argtable: %s \"%s\"\n", error ? error : _TREXC("undefined"), priv->pattern)); - ARG_LOG(("argtable: Bad argument table.\n")); - } - - trex_free(rex); - - ARG_TRACE(("arg_rexn() returns %p\n", result)); - return result; -} - -/* see copyright notice in trex.h */ -#include -#include -#include -#include - -#ifdef _UINCODE -#define scisprint iswprint -#define scstrlen wcslen -#define scprintf wprintf -#define _SC(x) L(x) -#else -#define scisprint isprint -#define scstrlen strlen -#define scprintf printf -#define _SC(x) (x) -#endif - -#ifdef ARG_REX_DEBUG -#include - -static const TRexChar* g_nnames[] = {_SC("NONE"), _SC("OP_GREEDY"), _SC("OP_OR"), _SC("OP_EXPR"), _SC("OP_NOCAPEXPR"), - _SC("OP_DOT"), _SC("OP_CLASS"), _SC("OP_CCLASS"), _SC("OP_NCLASS"), _SC("OP_RANGE"), - _SC("OP_CHAR"), _SC("OP_EOL"), _SC("OP_BOL"), _SC("OP_WB")}; - -#endif -#define OP_GREEDY (MAX_CHAR + 1) /* * + ? {n} */ -#define OP_OR (MAX_CHAR + 2) -#define OP_EXPR (MAX_CHAR + 3) /* parentesis () */ -#define OP_NOCAPEXPR (MAX_CHAR + 4) /* parentesis (?:) */ -#define OP_DOT (MAX_CHAR + 5) -#define OP_CLASS (MAX_CHAR + 6) -#define OP_CCLASS (MAX_CHAR + 7) -#define OP_NCLASS (MAX_CHAR + 8) /* negates class the [^ */ -#define OP_RANGE (MAX_CHAR + 9) -#define OP_CHAR (MAX_CHAR + 10) -#define OP_EOL (MAX_CHAR + 11) -#define OP_BOL (MAX_CHAR + 12) -#define OP_WB (MAX_CHAR + 13) - -#define TREX_SYMBOL_ANY_CHAR ('.') -#define TREX_SYMBOL_GREEDY_ONE_OR_MORE ('+') -#define TREX_SYMBOL_GREEDY_ZERO_OR_MORE ('*') -#define TREX_SYMBOL_GREEDY_ZERO_OR_ONE ('?') -#define TREX_SYMBOL_BRANCH ('|') -#define TREX_SYMBOL_END_OF_STRING ('$') -#define TREX_SYMBOL_BEGINNING_OF_STRING ('^') -#define TREX_SYMBOL_ESCAPE_CHAR ('\\') - -typedef int TRexNodeType; - -typedef struct tagTRexNode { - TRexNodeType type; - int left; - int right; - int next; -} TRexNode; - -struct TRex { - const TRexChar* _eol; - const TRexChar* _bol; - const TRexChar* _p; - int _first; - int _op; - TRexNode* _nodes; - int _nallocated; - int _nsize; - int _nsubexpr; - TRexMatch* _matches; - int _currsubexp; - void* _jmpbuf; - const TRexChar** _error; - int _flags; -}; - -static int trex_list(TRex* exp); - -static int trex_newnode(TRex* exp, TRexNodeType type) { - TRexNode n; - int newid; - n.type = type; - n.next = n.right = n.left = -1; - if (type == OP_EXPR) - n.right = exp->_nsubexpr++; - if (exp->_nallocated < (exp->_nsize + 1)) { - exp->_nallocated *= 2; - exp->_nodes = (TRexNode*)xrealloc(exp->_nodes, (size_t)exp->_nallocated * sizeof(TRexNode)); - } - exp->_nodes[exp->_nsize++] = n; - newid = exp->_nsize - 1; - return (int)newid; -} - -static void trex_error(TRex* exp, const TRexChar* error) { - if (exp->_error) - *exp->_error = error; - longjmp(*((jmp_buf*)exp->_jmpbuf), -1); -} - -static void trex_expect(TRex* exp, int n) { - if ((*exp->_p) != n) - trex_error(exp, _SC("expected paren")); - exp->_p++; -} - -static TRexChar trex_escapechar(TRex* exp) { - if (*exp->_p == TREX_SYMBOL_ESCAPE_CHAR) { - exp->_p++; - switch (*exp->_p) { - case 'v': - exp->_p++; - return '\v'; - case 'n': - exp->_p++; - return '\n'; - case 't': - exp->_p++; - return '\t'; - case 'r': - exp->_p++; - return '\r'; - case 'f': - exp->_p++; - return '\f'; - default: - return (*exp->_p++); - } - } else if (!scisprint((int)(*exp->_p))) - trex_error(exp, _SC("letter expected")); - return (*exp->_p++); -} - -static int trex_charclass(TRex* exp, int classid) { - int n = trex_newnode(exp, OP_CCLASS); - exp->_nodes[n].left = classid; - return n; -} - -static int trex_charnode(TRex* exp, TRexBool isclass) { - TRexChar t; - if (*exp->_p == TREX_SYMBOL_ESCAPE_CHAR) { - exp->_p++; - switch (*exp->_p) { - case 'n': - exp->_p++; - return trex_newnode(exp, '\n'); - case 't': - exp->_p++; - return trex_newnode(exp, '\t'); - case 'r': - exp->_p++; - return trex_newnode(exp, '\r'); - case 'f': - exp->_p++; - return trex_newnode(exp, '\f'); - case 'v': - exp->_p++; - return trex_newnode(exp, '\v'); - case 'a': - case 'A': - case 'w': - case 'W': - case 's': - case 'S': - case 'd': - case 'D': - case 'x': - case 'X': - case 'c': - case 'C': - case 'p': - case 'P': - case 'l': - case 'u': { - t = *exp->_p; - exp->_p++; - return trex_charclass(exp, t); - } - case 'b': - case 'B': - if (!isclass) { - int node = trex_newnode(exp, OP_WB); - exp->_nodes[node].left = *exp->_p; - exp->_p++; - return node; - } - /* fall through */ - default: - t = *exp->_p; - exp->_p++; - return trex_newnode(exp, t); - } - } else if (!scisprint((int)(*exp->_p))) { - trex_error(exp, _SC("letter expected")); - } - t = *exp->_p; - exp->_p++; - return trex_newnode(exp, t); -} -static int trex_class(TRex* exp) { - int ret = -1; - int first = -1, chain; - if (*exp->_p == TREX_SYMBOL_BEGINNING_OF_STRING) { - ret = trex_newnode(exp, OP_NCLASS); - exp->_p++; - } else - ret = trex_newnode(exp, OP_CLASS); - - if (*exp->_p == ']') - trex_error(exp, _SC("empty class")); - chain = ret; - while (*exp->_p != ']' && exp->_p != exp->_eol) { - if (*exp->_p == '-' && first != -1) { - int r, t; - if (*exp->_p++ == ']') - trex_error(exp, _SC("unfinished range")); - r = trex_newnode(exp, OP_RANGE); - if (first > *exp->_p) - trex_error(exp, _SC("invalid range")); - if (exp->_nodes[first].type == OP_CCLASS) - trex_error(exp, _SC("cannot use character classes in ranges")); - exp->_nodes[r].left = exp->_nodes[first].type; - t = trex_escapechar(exp); - exp->_nodes[r].right = t; - exp->_nodes[chain].next = r; - chain = r; - first = -1; - } else { - if (first != -1) { - int c = first; - exp->_nodes[chain].next = c; - chain = c; - first = trex_charnode(exp, TRex_True); - } else { - first = trex_charnode(exp, TRex_True); - } - } - } - if (first != -1) { - int c = first; - exp->_nodes[chain].next = c; - chain = c; - first = -1; - } - /* hack? */ - exp->_nodes[ret].left = exp->_nodes[ret].next; - exp->_nodes[ret].next = -1; - return ret; -} - -static int trex_parsenumber(TRex* exp) { - int ret = *exp->_p - '0'; - int positions = 10; - exp->_p++; - while (isdigit((int)(*exp->_p))) { - ret = ret * 10 + (*exp->_p++ - '0'); - if (positions == 1000000000) - trex_error(exp, _SC("overflow in numeric constant")); - positions *= 10; - }; - return ret; -} - -static int trex_element(TRex* exp) { - int ret = -1; - switch (*exp->_p) { - case '(': { - int expr, newn; - exp->_p++; - - if (*exp->_p == '?') { - exp->_p++; - trex_expect(exp, ':'); - expr = trex_newnode(exp, OP_NOCAPEXPR); - } else - expr = trex_newnode(exp, OP_EXPR); - newn = trex_list(exp); - exp->_nodes[expr].left = newn; - ret = expr; - trex_expect(exp, ')'); - } break; - case '[': - exp->_p++; - ret = trex_class(exp); - trex_expect(exp, ']'); - break; - case TREX_SYMBOL_END_OF_STRING: - exp->_p++; - ret = trex_newnode(exp, OP_EOL); - break; - case TREX_SYMBOL_ANY_CHAR: - exp->_p++; - ret = trex_newnode(exp, OP_DOT); - break; - default: - ret = trex_charnode(exp, TRex_False); - break; - } - - { - TRexBool isgreedy = TRex_False; - unsigned short p0 = 0, p1 = 0; - switch (*exp->_p) { - case TREX_SYMBOL_GREEDY_ZERO_OR_MORE: - p0 = 0; - p1 = 0xFFFF; - exp->_p++; - isgreedy = TRex_True; - break; - case TREX_SYMBOL_GREEDY_ONE_OR_MORE: - p0 = 1; - p1 = 0xFFFF; - exp->_p++; - isgreedy = TRex_True; - break; - case TREX_SYMBOL_GREEDY_ZERO_OR_ONE: - p0 = 0; - p1 = 1; - exp->_p++; - isgreedy = TRex_True; - break; - case '{': - exp->_p++; - if (!isdigit((int)(*exp->_p))) - trex_error(exp, _SC("number expected")); - p0 = (unsigned short)trex_parsenumber(exp); - /*******************************/ - switch (*exp->_p) { - case '}': - p1 = p0; - exp->_p++; - break; - case ',': - exp->_p++; - p1 = 0xFFFF; - if (isdigit((int)(*exp->_p))) { - p1 = (unsigned short)trex_parsenumber(exp); - } - trex_expect(exp, '}'); - break; - default: - trex_error(exp, _SC(", or } expected")); - } - /*******************************/ - isgreedy = TRex_True; - break; - } - if (isgreedy) { - int nnode = trex_newnode(exp, OP_GREEDY); - exp->_nodes[nnode].left = ret; - exp->_nodes[nnode].right = ((p0) << 16) | p1; - ret = nnode; - } - } - if ((*exp->_p != TREX_SYMBOL_BRANCH) && (*exp->_p != ')') && (*exp->_p != TREX_SYMBOL_GREEDY_ZERO_OR_MORE) && - (*exp->_p != TREX_SYMBOL_GREEDY_ONE_OR_MORE) && (*exp->_p != '\0')) { - int nnode = trex_element(exp); - exp->_nodes[ret].next = nnode; - } - - return ret; -} - -static int trex_list(TRex* exp) { - int ret = -1, e; - if (*exp->_p == TREX_SYMBOL_BEGINNING_OF_STRING) { - exp->_p++; - ret = trex_newnode(exp, OP_BOL); - } - e = trex_element(exp); - if (ret != -1) { - exp->_nodes[ret].next = e; - } else - ret = e; - - if (*exp->_p == TREX_SYMBOL_BRANCH) { - int temp, tright; - exp->_p++; - temp = trex_newnode(exp, OP_OR); - exp->_nodes[temp].left = ret; - tright = trex_list(exp); - exp->_nodes[temp].right = tright; - ret = temp; - } - return ret; -} - -static TRexBool trex_matchcclass(int cclass, TRexChar c) { - switch (cclass) { - case 'a': - return isalpha(c) ? TRex_True : TRex_False; - case 'A': - return !isalpha(c) ? TRex_True : TRex_False; - case 'w': - return (isalnum(c) || c == '_') ? TRex_True : TRex_False; - case 'W': - return (!isalnum(c) && c != '_') ? TRex_True : TRex_False; - case 's': - return isspace(c) ? TRex_True : TRex_False; - case 'S': - return !isspace(c) ? TRex_True : TRex_False; - case 'd': - return isdigit(c) ? TRex_True : TRex_False; - case 'D': - return !isdigit(c) ? TRex_True : TRex_False; - case 'x': - return isxdigit(c) ? TRex_True : TRex_False; - case 'X': - return !isxdigit(c) ? TRex_True : TRex_False; - case 'c': - return iscntrl(c) ? TRex_True : TRex_False; - case 'C': - return !iscntrl(c) ? TRex_True : TRex_False; - case 'p': - return ispunct(c) ? TRex_True : TRex_False; - case 'P': - return !ispunct(c) ? TRex_True : TRex_False; - case 'l': - return islower(c) ? TRex_True : TRex_False; - case 'u': - return isupper(c) ? TRex_True : TRex_False; - } - return TRex_False; /*cannot happen*/ -} - -static TRexBool trex_matchclass(TRex* exp, TRexNode* node, TRexChar c) { - do { - switch (node->type) { - case OP_RANGE: - if (exp->_flags & TREX_ICASE) { - if (c >= toupper(node->left) && c <= toupper(node->right)) - return TRex_True; - if (c >= tolower(node->left) && c <= tolower(node->right)) - return TRex_True; - } else { - if (c >= node->left && c <= node->right) - return TRex_True; - } - break; - case OP_CCLASS: - if (trex_matchcclass(node->left, c)) - return TRex_True; - break; - default: - if (exp->_flags & TREX_ICASE) { - if (c == tolower(node->type) || c == toupper(node->type)) - return TRex_True; - } else { - if (c == node->type) - return TRex_True; - } - } - } while ((node->next != -1) && ((node = &exp->_nodes[node->next]) != NULL)); - return TRex_False; -} - -static const TRexChar* trex_matchnode(TRex* exp, TRexNode* node, const TRexChar* str, TRexNode* next) { - TRexNodeType type = node->type; - switch (type) { - case OP_GREEDY: { - /* TRexNode *greedystop = (node->next != -1) ? &exp->_nodes[node->next] : NULL; */ - TRexNode* greedystop = NULL; - int p0 = (node->right >> 16) & 0x0000FFFF, p1 = node->right & 0x0000FFFF, nmaches = 0; - const TRexChar *s = str, *good = str; - - if (node->next != -1) { - greedystop = &exp->_nodes[node->next]; - } else { - greedystop = next; - } - - while ((nmaches == 0xFFFF || nmaches < p1)) { - const TRexChar* stop; - if ((s = trex_matchnode(exp, &exp->_nodes[node->left], s, greedystop)) == NULL) - break; - nmaches++; - good = s; - if (greedystop) { - /* checks that 0 matches satisfy the expression(if so skips) */ - /* if not would always stop(for instance if is a '?') */ - if (greedystop->type != OP_GREEDY || (greedystop->type == OP_GREEDY && ((greedystop->right >> 16) & 0x0000FFFF) != 0)) { - TRexNode* gnext = NULL; - if (greedystop->next != -1) { - gnext = &exp->_nodes[greedystop->next]; - } else if (next && next->next != -1) { - gnext = &exp->_nodes[next->next]; - } - stop = trex_matchnode(exp, greedystop, s, gnext); - if (stop) { - /* if satisfied stop it */ - if (p0 == p1 && p0 == nmaches) - break; - else if (nmaches >= p0 && p1 == 0xFFFF) - break; - else if (nmaches >= p0 && nmaches <= p1) - break; - } - } - } - - if (s >= exp->_eol) - break; - } - if (p0 == p1 && p0 == nmaches) - return good; - else if (nmaches >= p0 && p1 == 0xFFFF) - return good; - else if (nmaches >= p0 && nmaches <= p1) - return good; - return NULL; - } - case OP_OR: { - const TRexChar* asd = str; - TRexNode* temp = &exp->_nodes[node->left]; - while ((asd = trex_matchnode(exp, temp, asd, NULL)) != NULL) { - if (temp->next != -1) - temp = &exp->_nodes[temp->next]; - else - return asd; - } - asd = str; - temp = &exp->_nodes[node->right]; - while ((asd = trex_matchnode(exp, temp, asd, NULL)) != NULL) { - if (temp->next != -1) - temp = &exp->_nodes[temp->next]; - else - return asd; - } - return NULL; - break; - } - case OP_EXPR: - case OP_NOCAPEXPR: { - TRexNode* n = &exp->_nodes[node->left]; - const TRexChar* cur = str; - int capture = -1; - if (node->type != OP_NOCAPEXPR && node->right == exp->_currsubexp) { - capture = exp->_currsubexp; - exp->_matches[capture].begin = cur; - exp->_currsubexp++; - } - - do { - TRexNode* subnext = NULL; - if (n->next != -1) { - subnext = &exp->_nodes[n->next]; - } else { - subnext = next; - } - if ((cur = trex_matchnode(exp, n, cur, subnext)) == NULL) { - if (capture != -1) { - exp->_matches[capture].begin = 0; - exp->_matches[capture].len = 0; - } - return NULL; - } - } while ((n->next != -1) && ((n = &exp->_nodes[n->next]) != NULL)); - - if (capture != -1) - exp->_matches[capture].len = (int)(cur - exp->_matches[capture].begin); - return cur; - } - case OP_WB: - if ((str == exp->_bol && !isspace((int)(*str))) || (str == exp->_eol && !isspace((int)(*(str - 1)))) || (!isspace((int)(*str)) && isspace((int)(*(str + 1)))) || - (isspace((int)(*str)) && !isspace((int)(*(str + 1))))) { - return (node->left == 'b') ? str : NULL; - } - return (node->left == 'b') ? NULL : str; - case OP_BOL: - if (str == exp->_bol) - return str; - return NULL; - case OP_EOL: - if (str == exp->_eol) - return str; - return NULL; - case OP_DOT: { - str++; - } - return str; - case OP_NCLASS: - case OP_CLASS: - if (trex_matchclass(exp, &exp->_nodes[node->left], *str) ? (type == OP_CLASS ? TRex_True : TRex_False) - : (type == OP_NCLASS ? TRex_True : TRex_False)) { - str++; - return str; - } - return NULL; - case OP_CCLASS: - if (trex_matchcclass(node->left, *str)) { - str++; - return str; - } - return NULL; - default: /* char */ - if (exp->_flags & TREX_ICASE) { - if (*str != tolower(node->type) && *str != toupper(node->type)) - return NULL; - } else { - if (*str != node->type) - return NULL; - } - str++; - return str; - } -} - -/* public api */ -TRex* trex_compile(const TRexChar* pattern, const TRexChar** error, int flags) { - TRex* exp = (TRex*)xmalloc(sizeof(TRex)); - exp->_eol = exp->_bol = NULL; - exp->_p = pattern; - exp->_nallocated = (int)(scstrlen(pattern) * sizeof(TRexChar)); - exp->_nodes = (TRexNode*)xmalloc((size_t)exp->_nallocated * sizeof(TRexNode)); - exp->_nsize = 0; - exp->_matches = 0; - exp->_nsubexpr = 0; - exp->_first = trex_newnode(exp, OP_EXPR); - exp->_error = error; - exp->_jmpbuf = xmalloc(sizeof(jmp_buf)); - exp->_flags = flags; - if (setjmp(*((jmp_buf*)exp->_jmpbuf)) == 0) { - int res = trex_list(exp); - exp->_nodes[exp->_first].left = res; - if (*exp->_p != '\0') - trex_error(exp, _SC("unexpected character")); -#ifdef ARG_REX_DEBUG - { - int nsize, i; - nsize = exp->_nsize; - scprintf(_SC("\n")); - for (i = 0; i < nsize; i++) { - if (exp->_nodes[i].type > MAX_CHAR) - scprintf(_SC("[%02d] %10s "), i, g_nnames[exp->_nodes[i].type - MAX_CHAR]); - else - scprintf(_SC("[%02d] %10c "), i, exp->_nodes[i].type); - scprintf(_SC("left %02d right %02d next %02d\n"), exp->_nodes[i].left, exp->_nodes[i].right, exp->_nodes[i].next); - } - scprintf(_SC("\n")); - } -#endif - exp->_matches = (TRexMatch*)xmalloc((size_t)exp->_nsubexpr * sizeof(TRexMatch)); - memset(exp->_matches, 0, (size_t)exp->_nsubexpr * sizeof(TRexMatch)); - } else { - trex_free(exp); - return NULL; - } - return exp; -} - -void trex_free(TRex* exp) { - if (exp) { - xfree(exp->_nodes); - xfree(exp->_jmpbuf); - xfree(exp->_matches); - xfree(exp); - } -} - -TRexBool trex_match(TRex* exp, const TRexChar* text) { - const TRexChar* res = NULL; - exp->_bol = text; - exp->_eol = text + scstrlen(text); - exp->_currsubexp = 0; - res = trex_matchnode(exp, exp->_nodes, text, NULL); - if (res == NULL || res != exp->_eol) - return TRex_False; - return TRex_True; -} - -TRexBool trex_searchrange(TRex* exp, const TRexChar* text_begin, const TRexChar* text_end, const TRexChar** out_begin, const TRexChar** out_end) { - const TRexChar* cur = NULL; - int node = exp->_first; - if (text_begin >= text_end) - return TRex_False; - exp->_bol = text_begin; - exp->_eol = text_end; - do { - cur = text_begin; - while (node != -1) { - exp->_currsubexp = 0; - cur = trex_matchnode(exp, &exp->_nodes[node], cur, NULL); - if (!cur) - break; - node = exp->_nodes[node].next; - } - text_begin++; - } while (cur == NULL && text_begin != text_end); - - if (cur == NULL) - return TRex_False; - - --text_begin; - - if (out_begin) - *out_begin = text_begin; - if (out_end) - *out_end = cur; - return TRex_True; -} - -TRexBool trex_search(TRex* exp, const TRexChar* text, const TRexChar** out_begin, const TRexChar** out_end) { - return trex_searchrange(exp, text, text + scstrlen(text), out_begin, out_end); -} - -int trex_getsubexpcount(TRex* exp) { - return exp->_nsubexpr; -} - -TRexBool trex_getsubexp(TRex* exp, int n, TRexMatch* subexp) { - if (n < 0 || n >= exp->_nsubexpr) - return TRex_False; - *subexp = exp->_matches[n]; - return TRex_True; -} -/******************************************************************************* - * arg_str: Implements the str command-line option - * - * This file is part of the argtable3 library. - * - * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include "argtable3.h" - -#ifndef ARG_AMALGAMATION -#include "argtable3_private.h" -#endif - -#include - -static void arg_str_resetfn(struct arg_str* parent) { - int i; - - ARG_TRACE(("%s:resetfn(%p)\n", __FILE__, parent)); - for (i = 0; i < parent->count; i++) { - parent->sval[i] = ""; - } - parent->count = 0; -} - -static int arg_str_scanfn(struct arg_str* parent, const char* argval) { - int errorcode = 0; - - if (parent->count == parent->hdr.maxcount) { - /* maximum number of arguments exceeded */ - errorcode = ARG_ERR_MAXCOUNT; - } else if (!argval) { - /* a valid argument with no argument value was given. */ - /* This happens when an optional argument value was invoked. */ - /* leave parent argument value unaltered but still count the argument. */ - parent->count++; - } else { - parent->sval[parent->count++] = argval; - } - - ARG_TRACE(("%s:scanfn(%p) returns %d\n", __FILE__, parent, errorcode)); - return errorcode; -} - -static int arg_str_checkfn(struct arg_str* parent) { - int errorcode = (parent->count < parent->hdr.mincount) ? ARG_ERR_MINCOUNT : 0; - - ARG_TRACE(("%s:checkfn(%p) returns %d\n", __FILE__, parent, errorcode)); - return errorcode; -} - -static void arg_str_errorfn(struct arg_str* parent, arg_dstr_t ds, int errorcode, const char* argval, const char* progname) { - const char* shortopts = parent->hdr.shortopts; - const char* longopts = parent->hdr.longopts; - const char* datatype = parent->hdr.datatype; - - /* make argval NULL safe */ - argval = argval ? argval : ""; - - arg_dstr_catf(ds, "%s: ", progname); - switch (errorcode) { - case ARG_ERR_MINCOUNT: - arg_dstr_cat(ds, "missing option "); - arg_print_option_ds(ds, shortopts, longopts, datatype, "\n"); - break; - - case ARG_ERR_MAXCOUNT: - arg_dstr_cat(ds, "excess option "); - arg_print_option_ds(ds, shortopts, longopts, argval, "\n"); - break; - } -} - -struct arg_str* arg_str0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary) { - return arg_strn(shortopts, longopts, datatype, 0, 1, glossary); -} - -struct arg_str* arg_str1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary) { - return arg_strn(shortopts, longopts, datatype, 1, 1, glossary); -} - -struct arg_str* arg_strn(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary) { - size_t nbytes; - struct arg_str* result; - int i; - - /* should not allow this stupid error */ - /* we should return an error code warning this logic error */ - /* foolproof things by ensuring maxcount is not less than mincount */ - maxcount = (maxcount < mincount) ? mincount : maxcount; - - nbytes = sizeof(struct arg_str) /* storage for struct arg_str */ - + (size_t)maxcount * sizeof(char*); /* storage for sval[maxcount] array */ - - result = (struct arg_str*)xmalloc(nbytes); - - /* init the arg_hdr struct */ - result->hdr.flag = ARG_HASVALUE; - result->hdr.shortopts = shortopts; - result->hdr.longopts = longopts; - result->hdr.datatype = datatype ? datatype : ""; - result->hdr.glossary = glossary; - result->hdr.mincount = mincount; - result->hdr.maxcount = maxcount; - result->hdr.parent = result; - result->hdr.resetfn = (arg_resetfn*)arg_str_resetfn; - result->hdr.scanfn = (arg_scanfn*)arg_str_scanfn; - result->hdr.checkfn = (arg_checkfn*)arg_str_checkfn; - result->hdr.errorfn = (arg_errorfn*)arg_str_errorfn; - - /* store the sval[maxcount] array immediately after the arg_str struct */ - result->sval = (const char**)(result + 1); - result->count = 0; - - /* foolproof the string pointers by initializing them to reference empty strings */ - for (i = 0; i < maxcount; i++) - result->sval[i] = ""; - - ARG_TRACE(("arg_strn() returns %p\n", result)); - return result; -} -/******************************************************************************* - * arg_cmd: Provides the sub-command mechanism - * - * This file is part of the argtable3 library. - * - * Copyright (C) 2013-2019 Tom G. Huang - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include "argtable3.h" - -#ifndef ARG_AMALGAMATION -#include "argtable3_private.h" -#endif - -#include -#include -#include - -#define MAX_MODULE_VERSION_SIZE 128 - -static arg_hashtable_t* s_hashtable = NULL; -static char* s_module_name = NULL; -static int s_mod_ver_major = 0; -static int s_mod_ver_minor = 0; -static int s_mod_ver_patch = 0; -static char* s_mod_ver_tag = NULL; -static char* s_mod_ver = NULL; - -void arg_set_module_name(const char* name) { - size_t slen; - - xfree(s_module_name); - slen = strlen(name); - s_module_name = (char*)xmalloc(slen + 1); - memset(s_module_name, 0, slen + 1); - -#if (defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || (defined(__STDC_SECURE_LIB__) && defined(__STDC_WANT_SECURE_LIB__)) - strncpy_s(s_module_name, slen + 1, name, slen); -#else - memcpy(s_module_name, name, slen); -#endif -} - -void arg_set_module_version(int major, int minor, int patch, const char* tag) { - size_t slen_tag, slen_ds; - arg_dstr_t ds; - - s_mod_ver_major = major; - s_mod_ver_minor = minor; - s_mod_ver_patch = patch; - - xfree(s_mod_ver_tag); - slen_tag = strlen(tag); - s_mod_ver_tag = (char*)xmalloc(slen_tag + 1); - memset(s_mod_ver_tag, 0, slen_tag + 1); - -#if (defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || (defined(__STDC_SECURE_LIB__) && defined(__STDC_WANT_SECURE_LIB__)) - strncpy_s(s_mod_ver_tag, slen_tag + 1, tag, slen_tag); -#else - memcpy(s_mod_ver_tag, tag, slen_tag); -#endif - - ds = arg_dstr_create(); - arg_dstr_catf(ds, "%d.", s_mod_ver_major); - arg_dstr_catf(ds, "%d.", s_mod_ver_minor); - arg_dstr_catf(ds, "%d.", s_mod_ver_patch); - arg_dstr_cat(ds, s_mod_ver_tag); - - xfree(s_mod_ver); - slen_ds = strlen(arg_dstr_cstr(ds)); - s_mod_ver = (char*)xmalloc(slen_ds + 1); - memset(s_mod_ver, 0, slen_ds + 1); - -#if (defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || (defined(__STDC_SECURE_LIB__) && defined(__STDC_WANT_SECURE_LIB__)) - strncpy_s(s_mod_ver, slen_ds + 1, arg_dstr_cstr(ds), slen_ds); -#else - memcpy(s_mod_ver, arg_dstr_cstr(ds), slen_ds); -#endif - - arg_dstr_destroy(ds); -} - -static unsigned int hash_key(const void* key) { - const char* str = (const char*)key; - int c; - unsigned int hash = 5381; - - while ((c = *str++) != 0) - hash = ((hash << 5) + hash) + (unsigned int)c; /* hash * 33 + c */ - - return hash; -} - -static int equal_keys(const void* key1, const void* key2) { - char* k1 = (char*)key1; - char* k2 = (char*)key2; - return (0 == strcmp(k1, k2)); -} - -void arg_cmd_init(void) { - s_hashtable = arg_hashtable_create(32, hash_key, equal_keys); -} - -void arg_cmd_uninit(void) { - arg_hashtable_destroy(s_hashtable, 1); -} - -void arg_cmd_register(const char* name, arg_cmdfn* proc, const char* description) { - arg_cmd_info_t* cmd_info; - size_t slen_name; - void* k; - - assert(strlen(name) < ARG_CMD_NAME_LEN); - assert(strlen(description) < ARG_CMD_DESCRIPTION_LEN); - - /* Check if the command already exists. */ - /* If the command exists, replace the existing command. */ - /* If the command doesn't exist, insert the command. */ - cmd_info = (arg_cmd_info_t*)arg_hashtable_search(s_hashtable, name); - if (cmd_info) { - arg_hashtable_remove(s_hashtable, name); - cmd_info = NULL; - } - - cmd_info = (arg_cmd_info_t*)xmalloc(sizeof(arg_cmd_info_t)); - memset(cmd_info, 0, sizeof(arg_cmd_info_t)); - -#if (defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || (defined(__STDC_SECURE_LIB__) && defined(__STDC_WANT_SECURE_LIB__)) - strncpy_s(cmd_info->name, ARG_CMD_NAME_LEN, name, strlen(name)); - strncpy_s(cmd_info->description, ARG_CMD_DESCRIPTION_LEN, description, strlen(description)); -#else - memcpy(cmd_info->name, name, strlen(name)); - memcpy(cmd_info->description, description, strlen(description)); -#endif - - cmd_info->proc = proc; - - slen_name = strlen(name); - k = xmalloc(slen_name + 1); - memset(k, 0, slen_name + 1); - -#if (defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || (defined(__STDC_SECURE_LIB__) && defined(__STDC_WANT_SECURE_LIB__)) - strncpy_s((char*)k, slen_name + 1, name, slen_name); -#else - memcpy((char*)k, name, slen_name); -#endif - - arg_hashtable_insert(s_hashtable, k, cmd_info); -} - -void arg_cmd_unregister(const char* name) { - arg_hashtable_remove(s_hashtable, name); -} - -int arg_cmd_dispatch(const char* name, int argc, char* argv[], arg_dstr_t res) { - arg_cmd_info_t* cmd_info = arg_cmd_info(name); - - assert(cmd_info != NULL); - assert(cmd_info->proc != NULL); - - return cmd_info->proc(argc, argv, res); -} - -arg_cmd_info_t* arg_cmd_info(const char* name) { - return (arg_cmd_info_t*)arg_hashtable_search(s_hashtable, name); -} - -unsigned int arg_cmd_count(void) { - return arg_hashtable_count(s_hashtable); -} - -arg_cmd_itr_t arg_cmd_itr_create(void) { - return (arg_cmd_itr_t)arg_hashtable_itr_create(s_hashtable); -} - -int arg_cmd_itr_advance(arg_cmd_itr_t itr) { - return arg_hashtable_itr_advance((arg_hashtable_itr_t*)itr); -} - -char* arg_cmd_itr_key(arg_cmd_itr_t itr) { - return (char*)arg_hashtable_itr_key((arg_hashtable_itr_t*)itr); -} - -arg_cmd_info_t* arg_cmd_itr_value(arg_cmd_itr_t itr) { - return (arg_cmd_info_t*)arg_hashtable_itr_value((arg_hashtable_itr_t*)itr); -} - -void arg_cmd_itr_destroy(arg_cmd_itr_t itr) { - arg_hashtable_itr_destroy((arg_hashtable_itr_t*)itr); -} - -int arg_cmd_itr_search(arg_cmd_itr_t itr, void* k) { - return arg_hashtable_itr_search((arg_hashtable_itr_t*)itr, s_hashtable, k); -} - -static const char* module_name(void) { - if (s_module_name == NULL || strlen(s_module_name) == 0) - return ""; - - return s_module_name; -} - -static const char* module_version(void) { - if (s_mod_ver == NULL || strlen(s_mod_ver) == 0) - return "0.0.0.0"; - - return s_mod_ver; -} - -void arg_make_get_help_msg(arg_dstr_t res) { - arg_dstr_catf(res, "%s v%s\n", module_name(), module_version()); - arg_dstr_catf(res, "Please type '%s help' to get more information.\n", module_name()); -} - -void arg_make_help_msg(arg_dstr_t ds, char* cmd_name, void** argtable) { - arg_cmd_info_t* cmd_info = (arg_cmd_info_t*)arg_hashtable_search(s_hashtable, cmd_name); - if (cmd_info) { - arg_dstr_catf(ds, "%s: %s\n", cmd_name, cmd_info->description); - } - - arg_dstr_cat(ds, "Usage:\n"); - arg_dstr_catf(ds, " %s", module_name()); - - arg_print_syntaxv_ds(ds, argtable, "\n \nAvailable options:\n"); - arg_print_glossary_ds(ds, argtable, " %-23s %s\n"); - - arg_dstr_cat(ds, "\n"); -} - -void arg_make_syntax_err_msg(arg_dstr_t ds, void** argtable, struct arg_end* end) { - arg_print_errors_ds(ds, end, module_name()); - arg_dstr_cat(ds, "Usage: \n"); - arg_dstr_catf(ds, " %s", module_name()); - arg_print_syntaxv_ds(ds, argtable, "\n"); - arg_dstr_cat(ds, "\n"); -} - -int arg_make_syntax_err_help_msg(arg_dstr_t ds, char* name, int help, int nerrors, void** argtable, struct arg_end* end, int* exitcode) { - /* help handling - * note: '-h|--help' takes precedence over error reporting - */ - if (help > 0) { - arg_make_help_msg(ds, name, argtable); - *exitcode = EXIT_SUCCESS; - return 1; - } - - /* syntax error handling */ - if (nerrors > 0) { - arg_make_syntax_err_msg(ds, argtable, end); - *exitcode = EXIT_FAILURE; - return 1; - } - - return 0; -} -/******************************************************************************* - * argtable3: Implements the main interfaces of the library - * - * This file is part of the argtable3 library. - * - * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include "argtable3.h" - -#ifndef ARG_AMALGAMATION -#include "argtable3_private.h" -#if ARG_REPLACE_GETOPT == 1 -#include "arg_getopt.h" -#else -#include -#endif -#else -#if ARG_REPLACE_GETOPT == 0 -#include -#endif -#endif - -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include -#undef WIN32_LEAN_AND_MEAN -#endif - -#include -#include -#include -#include -#include - -static void arg_register_error(struct arg_end* end, void* parent, int error, const char* argval) { - /* printf("arg_register_error(%p,%p,%d,%s)\n",end,parent,error,argval); */ - if (end->count < end->hdr.maxcount) { - end->error[end->count] = error; - end->parent[end->count] = parent; - end->argval[end->count] = argval; - end->count++; - } else { - end->error[end->hdr.maxcount - 1] = ARG_ELIMIT; - end->parent[end->hdr.maxcount - 1] = end; - end->argval[end->hdr.maxcount - 1] = NULL; - } -} - -/* - * Return index of first table entry with a matching short option - * or -1 if no match was found. - */ -static int find_shortoption(struct arg_hdr** table, char shortopt) { - int tabindex; - for (tabindex = 0; !(table[tabindex]->flag & ARG_TERMINATOR); tabindex++) { - if (table[tabindex]->shortopts && strchr(table[tabindex]->shortopts, shortopt)) - return tabindex; - } - return -1; -} - -struct longoptions { - int getoptval; - int noptions; - struct option* options; -}; - -#if 0 -static -void dump_longoptions(struct longoptions * longoptions) -{ - int i; - printf("getoptval = %d\n", longoptions->getoptval); - printf("noptions = %d\n", longoptions->noptions); - for (i = 0; i < longoptions->noptions; i++) - { - printf("options[%d].name = \"%s\"\n", - i, - longoptions->options[i].name); - printf("options[%d].has_arg = %d\n", i, longoptions->options[i].has_arg); - printf("options[%d].flag = %p\n", i, longoptions->options[i].flag); - printf("options[%d].val = %d\n", i, longoptions->options[i].val); - } -} -#endif - -static struct longoptions* alloc_longoptions(struct arg_hdr** table) { - struct longoptions* result; - size_t nbytes; - int noptions = 1; - size_t longoptlen = 0; - int tabindex; - int option_index = 0; - char* store; - - /* - * Determine the total number of option structs required - * by counting the number of comma separated long options - * in all table entries and return the count in noptions. - * note: noptions starts at 1 not 0 because we getoptlong - * requires a NULL option entry to terminate the option array. - * While we are at it, count the number of chars required - * to store private copies of all the longoption strings - * and return that count in logoptlen. - */ - tabindex = 0; - do { - const char* longopts = table[tabindex]->longopts; - longoptlen += (longopts ? strlen(longopts) : 0) + 1; - while (longopts) { - noptions++; - longopts = strchr(longopts + 1, ','); - } - } while (!(table[tabindex++]->flag & ARG_TERMINATOR)); - /*printf("%d long options consuming %d chars in total\n",noptions,longoptlen);*/ - - /* allocate storage for return data structure as: */ - /* (struct longoptions) + (struct options)[noptions] + char[longoptlen] */ - nbytes = sizeof(struct longoptions) + sizeof(struct option) * (size_t)noptions + longoptlen; - result = (struct longoptions*)xmalloc(nbytes); - - result->getoptval = 0; - result->noptions = noptions; - result->options = (struct option*)(result + 1); - store = (char*)(result->options + noptions); - - for (tabindex = 0; !(table[tabindex]->flag & ARG_TERMINATOR); tabindex++) { - const char* longopts = table[tabindex]->longopts; - - while (longopts && *longopts) { - char* storestart = store; - - /* copy progressive longopt strings into the store */ - while (*longopts != 0 && *longopts != ',') - *store++ = *longopts++; - *store++ = 0; - if (*longopts == ',') - longopts++; - /*fprintf(stderr,"storestart=\"%s\"\n",storestart);*/ - - result->options[option_index].name = storestart; - result->options[option_index].flag = &(result->getoptval); - result->options[option_index].val = tabindex; - if (table[tabindex]->flag & ARG_HASOPTVALUE) - result->options[option_index].has_arg = 2; - else if (table[tabindex]->flag & ARG_HASVALUE) - result->options[option_index].has_arg = 1; - else - result->options[option_index].has_arg = 0; - - option_index++; - } - } - /* terminate the options array with a zero-filled entry */ - result->options[option_index].name = 0; - result->options[option_index].has_arg = 0; - result->options[option_index].flag = 0; - result->options[option_index].val = 0; - - /*dump_longoptions(result);*/ - return result; -} - -static char* alloc_shortoptions(struct arg_hdr** table) { - char* result; - size_t len = 2; - int tabindex; - char* res; - - /* determine the total number of option chars required */ - for (tabindex = 0; !(table[tabindex]->flag & ARG_TERMINATOR); tabindex++) { - struct arg_hdr* hdr = table[tabindex]; - len += 3 * (hdr->shortopts ? strlen(hdr->shortopts) : 0); - } - - result = xmalloc(len); - - res = result; - - /* add a leading ':' so getopt return codes distinguish */ - /* unrecognised option and options missing argument values */ - *res++ = ':'; - - for (tabindex = 0; !(table[tabindex]->flag & ARG_TERMINATOR); tabindex++) { - struct arg_hdr* hdr = table[tabindex]; - const char* shortopts = hdr->shortopts; - while (shortopts && *shortopts) { - *res++ = *shortopts++; - if (hdr->flag & ARG_HASVALUE) - *res++ = ':'; - if (hdr->flag & ARG_HASOPTVALUE) - *res++ = ':'; - } - } - /* null terminate the string */ - *res = 0; - - /*printf("alloc_shortoptions() returns \"%s\"\n",(result?result:"NULL"));*/ - return result; -} - -/* return index of the table terminator entry */ -static int arg_endindex(struct arg_hdr** table) { - int tabindex = 0; - while (!(table[tabindex]->flag & ARG_TERMINATOR)) - tabindex++; - return tabindex; -} - -static void arg_parse_tagged(int argc, char** argv, struct arg_hdr** table, struct arg_end* endtable) { - struct longoptions* longoptions; - char* shortoptions; - int copt; - - /*printf("arg_parse_tagged(%d,%p,%p,%p)\n",argc,argv,table,endtable);*/ - - /* allocate short and long option arrays for the given opttable[]. */ - /* if the allocs fail then put an error msg in the last table entry. */ - longoptions = alloc_longoptions(table); - shortoptions = alloc_shortoptions(table); - - /*dump_longoptions(longoptions);*/ - - /* reset getopts internal option-index to zero, and disable error reporting */ - optind = 0; - opterr = 0; - - /* fetch and process args using getopt_long */ -#ifdef ARG_LONG_ONLY - while ((copt = getopt_long_only(argc, argv, shortoptions, longoptions->options, NULL)) != -1) { -#else - while ((copt = getopt_long(argc, argv, shortoptions, longoptions->options, NULL)) != -1) { -#endif - /* - printf("optarg='%s'\n",optarg); - printf("optind=%d\n",optind); - printf("copt=%c\n",(char)copt); - printf("optopt=%c (%d)\n",optopt, (int)(optopt)); - */ - switch (copt) { - case 0: { - int tabindex = longoptions->getoptval; - void* parent = table[tabindex]->parent; - /*printf("long option detected from argtable[%d]\n", tabindex);*/ - if (optarg && optarg[0] == 0 && (table[tabindex]->flag & ARG_HASVALUE)) { - /* printf(": long option %s requires an argument\n",argv[optind-1]); */ - arg_register_error(endtable, endtable, ARG_EMISSARG, argv[optind - 1]); - /* continue to scan the (empty) argument value to enforce argument count checking */ - } - if (table[tabindex]->scanfn) { - int errorcode = table[tabindex]->scanfn(parent, optarg); - if (errorcode != 0) - arg_register_error(endtable, parent, errorcode, optarg); - } - } break; - - case '?': - /* - * getopt_long() found an unrecognised short option. - * if it was a short option its value is in optopt - * if it was a long option then optopt=0 - */ - switch (optopt) { - case 0: - /*printf("?0 unrecognised long option %s\n",argv[optind-1]);*/ - arg_register_error(endtable, endtable, ARG_ELONGOPT, argv[optind - 1]); - break; - default: - /*printf("?* unrecognised short option '%c'\n",optopt);*/ - arg_register_error(endtable, endtable, optopt, NULL); - break; - } - break; - - case ':': - /* - * getopt_long() found an option with its argument missing. - */ - /*printf(": option %s requires an argument\n",argv[optind-1]); */ - arg_register_error(endtable, endtable, ARG_EMISSARG, argv[optind - 1]); - break; - - default: { - /* getopt_long() found a valid short option */ - int tabindex = find_shortoption(table, (char)copt); - /*printf("short option detected from argtable[%d]\n", tabindex);*/ - if (tabindex == -1) { - /* should never get here - but handle it just in case */ - /*printf("unrecognised short option %d\n",copt);*/ - arg_register_error(endtable, endtable, copt, NULL); - } else { - if (table[tabindex]->scanfn) { - void* parent = table[tabindex]->parent; - int errorcode = table[tabindex]->scanfn(parent, optarg); - if (errorcode != 0) - arg_register_error(endtable, parent, errorcode, optarg); - } - } - break; - } - } - } - - xfree(shortoptions); - xfree(longoptions); -} - -static void arg_parse_untagged(int argc, char** argv, struct arg_hdr** table, struct arg_end* endtable) { - int tabindex = 0; - int errorlast = 0; - const char* optarglast = NULL; - void* parentlast = NULL; - - /*printf("arg_parse_untagged(%d,%p,%p,%p)\n",argc,argv,table,endtable);*/ - while (!(table[tabindex]->flag & ARG_TERMINATOR)) { - void* parent; - int errorcode; - - /* if we have exhausted our argv[optind] entries then we have finished */ - if (optind >= argc) { - /*printf("arg_parse_untagged(): argv[] exhausted\n");*/ - return; - } - - /* skip table entries with non-null long or short options (they are not untagged entries) */ - if (table[tabindex]->longopts || table[tabindex]->shortopts) { - /*printf("arg_parse_untagged(): skipping argtable[%d] (tagged argument)\n",tabindex);*/ - tabindex++; - continue; - } - - /* skip table entries with NULL scanfn */ - if (!(table[tabindex]->scanfn)) { - /*printf("arg_parse_untagged(): skipping argtable[%d] (NULL scanfn)\n",tabindex);*/ - tabindex++; - continue; - } - - /* attempt to scan the current argv[optind] with the current */ - /* table[tabindex] entry. If it succeeds then keep it, otherwise */ - /* try again with the next table[] entry. */ - parent = table[tabindex]->parent; - errorcode = table[tabindex]->scanfn(parent, argv[optind]); - if (errorcode == 0) { - /* success, move onto next argv[optind] but stay with same table[tabindex] */ - /*printf("arg_parse_untagged(): argtable[%d] successfully matched\n",tabindex);*/ - optind++; - - /* clear the last tentative error */ - errorlast = 0; - } else { - /* failure, try same argv[optind] with next table[tabindex] entry */ - /*printf("arg_parse_untagged(): argtable[%d] failed match\n",tabindex);*/ - tabindex++; - - /* remember this as a tentative error we may wish to reinstate later */ - errorlast = errorcode; - optarglast = argv[optind]; - parentlast = parent; - } - } - - /* if a tenative error still remains at this point then register it as a proper error */ - if (errorlast) { - arg_register_error(endtable, parentlast, errorlast, optarglast); - optind++; - } - - /* only get here when not all argv[] entries were consumed */ - /* register an error for each unused argv[] entry */ - while (optind < argc) { - /*printf("arg_parse_untagged(): argv[%d]=\"%s\" not consumed\n",optind,argv[optind]);*/ - arg_register_error(endtable, endtable, ARG_ENOMATCH, argv[optind++]); - } - - return; -} - -static void arg_parse_check(struct arg_hdr** table, struct arg_end* endtable) { - int tabindex = 0; - /* printf("arg_parse_check()\n"); */ - do { - if (table[tabindex]->checkfn) { - void* parent = table[tabindex]->parent; - int errorcode = table[tabindex]->checkfn(parent); - if (errorcode != 0) - arg_register_error(endtable, parent, errorcode, NULL); - } - } while (!(table[tabindex++]->flag & ARG_TERMINATOR)); -} - -static void arg_reset(void** argtable) { - struct arg_hdr** table = (struct arg_hdr**)argtable; - int tabindex = 0; - /*printf("arg_reset(%p)\n",argtable);*/ - do { - if (table[tabindex]->resetfn) - table[tabindex]->resetfn(table[tabindex]->parent); - } while (!(table[tabindex++]->flag & ARG_TERMINATOR)); -} - -int arg_parse(int argc, char** argv, void** argtable) { - struct arg_hdr** table = (struct arg_hdr**)argtable; - struct arg_end* endtable; - int endindex; - char** argvcopy = NULL; - int i; - - /*printf("arg_parse(%d,%p,%p)\n",argc,argv,argtable);*/ - - /* reset any argtable data from previous invocations */ - arg_reset(argtable); - - /* locate the first end-of-table marker within the array */ - endindex = arg_endindex(table); - endtable = (struct arg_end*)table[endindex]; - - /* Special case of argc==0. This can occur on Texas Instruments DSP. */ - /* Failure to trap this case results in an unwanted NULL result from */ - /* the malloc for argvcopy (next code block). */ - if (argc == 0) { - /* We must still perform post-parse checks despite the absence of command line arguments */ - arg_parse_check(table, endtable); - - /* Now we are finished */ - return endtable->count; - } - - argvcopy = (char**)xmalloc(sizeof(char*) * (size_t)(argc + 1)); - - /* - Fill in the local copy of argv[]. We need a local copy - because getopt rearranges argv[] which adversely affects - susbsequent parsing attempts. - */ - for (i = 0; i < argc; i++) - argvcopy[i] = argv[i]; - - argvcopy[argc] = NULL; - - /* parse the command line (local copy) for tagged options */ - arg_parse_tagged(argc, argvcopy, table, endtable); - - /* parse the command line (local copy) for untagged options */ - arg_parse_untagged(argc, argvcopy, table, endtable); - - /* if no errors so far then perform post-parse checks otherwise dont bother */ - if (endtable->count == 0) - arg_parse_check(table, endtable); - - /* release the local copt of argv[] */ - xfree(argvcopy); - - return endtable->count; -} - -/* - * Concatenate contents of src[] string onto *pdest[] string. - * The *pdest pointer is altered to point to the end of the - * target string and *pndest is decremented by the same number - * of chars. - * Does not append more than *pndest chars into *pdest[] - * so as to prevent buffer overruns. - * Its something like strncat() but more efficient for repeated - * calls on the same destination string. - * Example of use: - * char dest[30] = "good" - * size_t ndest = sizeof(dest); - * char *pdest = dest; - * arg_char(&pdest,"bye ",&ndest); - * arg_char(&pdest,"cruel ",&ndest); - * arg_char(&pdest,"world!",&ndest); - * Results in: - * dest[] == "goodbye cruel world!" - * ndest == 10 - */ -static void arg_cat(char** pdest, const char* src, size_t* pndest) { - char* dest = *pdest; - char* end = dest + *pndest; - - /*locate null terminator of dest string */ - while (dest < end-1 && *dest != 0) - dest++; - - /* concat src string to dest string */ - while (dest < end-1 && *src != 0) - *dest++ = *src++; - - /* null terminate dest string */ - *dest = 0; - - /* update *pdest and *pndest */ - *pndest = (size_t)(end - dest); - *pdest = dest; -} - -static void arg_cat_option(char* dest, size_t ndest, const char* shortopts, const char* longopts, const char* datatype, int optvalue) { - if (shortopts) { - char option[3]; - - /* note: option array[] is initialiazed dynamically here to satisfy */ - /* a deficiency in the watcom compiler wrt static array initializers. */ - option[0] = '-'; - option[1] = shortopts[0]; - option[2] = 0; - - arg_cat(&dest, option, &ndest); - if (datatype) { - arg_cat(&dest, " ", &ndest); - if (optvalue) { - arg_cat(&dest, "[", &ndest); - arg_cat(&dest, datatype, &ndest); - arg_cat(&dest, "]", &ndest); - } else - arg_cat(&dest, datatype, &ndest); - } - } else if (longopts) { - size_t ncspn; - - /* add "--" tag prefix */ - arg_cat(&dest, "--", &ndest); - - /* add comma separated option tag */ - ncspn = strcspn(longopts, ","); -#if (defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || (defined(__STDC_SECURE_LIB__) && defined(__STDC_WANT_SECURE_LIB__)) - strncat_s(dest, ndest, longopts, (ncspn < ndest) ? ncspn : ndest); -#else - strncat(dest, longopts, (ncspn < ndest) ? ncspn : ndest); -#endif - - if (datatype) { - arg_cat(&dest, "=", &ndest); - if (optvalue) { - arg_cat(&dest, "[", &ndest); - arg_cat(&dest, datatype, &ndest); - arg_cat(&dest, "]", &ndest); - } else - arg_cat(&dest, datatype, &ndest); - } - } else if (datatype) { - if (optvalue) { - arg_cat(&dest, "[", &ndest); - arg_cat(&dest, datatype, &ndest); - arg_cat(&dest, "]", &ndest); - } else - arg_cat(&dest, datatype, &ndest); - } -} - -static void arg_cat_optionv(char* dest, size_t ndest, const char* shortopts, const char* longopts, const char* datatype, int optvalue, const char* separator) { - separator = separator ? separator : ""; - - if (shortopts) { - const char* c = shortopts; - while (*c) { - /* "-a|-b|-c" */ - char shortopt[3]; - - /* note: shortopt array[] is initialiazed dynamically here to satisfy */ - /* a deficiency in the watcom compiler wrt static array initializers. */ - shortopt[0] = '-'; - shortopt[1] = *c; - shortopt[2] = 0; - - arg_cat(&dest, shortopt, &ndest); - if (*++c) - arg_cat(&dest, separator, &ndest); - } - } - - /* put separator between long opts and short opts */ - if (shortopts && longopts) - arg_cat(&dest, separator, &ndest); - - if (longopts) { - const char* c = longopts; - while (*c) { - size_t ncspn; - - /* add "--" tag prefix */ - arg_cat(&dest, "--", &ndest); - - /* add comma separated option tag */ - ncspn = strcspn(c, ","); -#if (defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || (defined(__STDC_SECURE_LIB__) && defined(__STDC_WANT_SECURE_LIB__)) - strncat_s(dest, ndest, c, (ncspn < ndest) ? ncspn : ndest); -#else - strncat(dest, c, (ncspn < ndest) ? ncspn : ndest); -#endif - c += ncspn; - - /* add given separator in place of comma */ - if (*c == ',') { - arg_cat(&dest, separator, &ndest); - c++; - } - } - } - - if (datatype) { - if (longopts) - arg_cat(&dest, "=", &ndest); - else if (shortopts) - arg_cat(&dest, " ", &ndest); - - if (optvalue) { - arg_cat(&dest, "[", &ndest); - arg_cat(&dest, datatype, &ndest); - arg_cat(&dest, "]", &ndest); - } else - arg_cat(&dest, datatype, &ndest); - } -} - -void arg_print_option_ds(arg_dstr_t ds, const char* shortopts, const char* longopts, const char* datatype, const char* suffix) { - char syntax[200] = ""; - suffix = suffix ? suffix : ""; - - /* there is no way of passing the proper optvalue for optional argument values here, so we must ignore it */ - arg_cat_optionv(syntax, sizeof(syntax) - 1, shortopts, longopts, datatype, 0, "|"); - - arg_dstr_cat(ds, syntax); - arg_dstr_cat(ds, (char*)suffix); -} - -/* this function should be deprecated because it doesn't consider optional argument values (ARG_HASOPTVALUE) */ -void arg_print_option(FILE* fp, const char* shortopts, const char* longopts, const char* datatype, const char* suffix) { - arg_dstr_t ds = arg_dstr_create(); - arg_print_option_ds(ds, shortopts, longopts, datatype, suffix); - fputs(arg_dstr_cstr(ds), fp); - arg_dstr_destroy(ds); -} - -/* - * Print a GNU style [OPTION] string in which all short options that - * do not take argument values are presented in abbreviated form, as - * in: -xvfsd, or -xvf[sd], or [-xvsfd] - */ -static void arg_print_gnuswitch_ds(arg_dstr_t ds, struct arg_hdr** table) { - int tabindex; - const char* format1 = " -%c"; - const char* format2 = " [-%c"; - const char* suffix = ""; - - /* print all mandatory switches that are without argument values */ - for (tabindex = 0; table[tabindex] && !(table[tabindex]->flag & ARG_TERMINATOR); tabindex++) { - /* skip optional options */ - if (table[tabindex]->mincount < 1) - continue; - - /* skip non-short options */ - if (table[tabindex]->shortopts == NULL) - continue; - - /* skip options that take argument values */ - if (table[tabindex]->flag & ARG_HASVALUE) - continue; - - /* print the short option (only the first short option char, ignore multiple choices)*/ - arg_dstr_catf(ds, format1, table[tabindex]->shortopts[0]); - format1 = "%c"; - format2 = "[%c"; - } - - /* print all optional switches that are without argument values */ - for (tabindex = 0; table[tabindex] && !(table[tabindex]->flag & ARG_TERMINATOR); tabindex++) { - /* skip mandatory args */ - if (table[tabindex]->mincount > 0) - continue; - - /* skip args without short options */ - if (table[tabindex]->shortopts == NULL) - continue; - - /* skip args with values */ - if (table[tabindex]->flag & ARG_HASVALUE) - continue; - - /* print first short option */ - arg_dstr_catf(ds, format2, table[tabindex]->shortopts[0]); - format2 = "%c"; - suffix = "]"; - } - - arg_dstr_catf(ds, "%s", suffix); -} - -void arg_print_syntax_ds(arg_dstr_t ds, void** argtable, const char* suffix) { - struct arg_hdr** table = (struct arg_hdr**)argtable; - int i, tabindex; - - /* print GNU style [OPTION] string */ - arg_print_gnuswitch_ds(ds, table); - - /* print remaining options in abbreviated style */ - for (tabindex = 0; table[tabindex] && !(table[tabindex]->flag & ARG_TERMINATOR); tabindex++) { - char syntax[200] = ""; - const char *shortopts, *longopts, *datatype; - - /* skip short options without arg values (they were printed by arg_print_gnu_switch) */ - if (table[tabindex]->shortopts && !(table[tabindex]->flag & ARG_HASVALUE)) - continue; - - shortopts = table[tabindex]->shortopts; - longopts = table[tabindex]->longopts; - datatype = table[tabindex]->datatype; - arg_cat_option(syntax, sizeof(syntax) - 1, shortopts, longopts, datatype, table[tabindex]->flag & ARG_HASOPTVALUE); - - if (strlen(syntax) > 0) { - /* print mandatory instances of this option */ - for (i = 0; i < table[tabindex]->mincount; i++) { - arg_dstr_cat(ds, " "); - arg_dstr_cat(ds, syntax); - } - - /* print optional instances enclosed in "[..]" */ - switch (table[tabindex]->maxcount - table[tabindex]->mincount) { - case 0: - break; - case 1: - arg_dstr_cat(ds, " ["); - arg_dstr_cat(ds, syntax); - arg_dstr_cat(ds, "]"); - break; - case 2: - arg_dstr_cat(ds, " ["); - arg_dstr_cat(ds, syntax); - arg_dstr_cat(ds, "]"); - arg_dstr_cat(ds, " ["); - arg_dstr_cat(ds, syntax); - arg_dstr_cat(ds, "]"); - break; - default: - arg_dstr_cat(ds, " ["); - arg_dstr_cat(ds, syntax); - arg_dstr_cat(ds, "]..."); - break; - } - } - } - - if (suffix) { - arg_dstr_cat(ds, (char*)suffix); - } -} - -void arg_print_syntax(FILE* fp, void** argtable, const char* suffix) { - arg_dstr_t ds = arg_dstr_create(); - arg_print_syntax_ds(ds, argtable, suffix); - fputs(arg_dstr_cstr(ds), fp); - arg_dstr_destroy(ds); -} - -void arg_print_syntaxv_ds(arg_dstr_t ds, void** argtable, const char* suffix) { - struct arg_hdr** table = (struct arg_hdr**)argtable; - int i, tabindex; - - /* print remaining options in abbreviated style */ - for (tabindex = 0; table[tabindex] && !(table[tabindex]->flag & ARG_TERMINATOR); tabindex++) { - char syntax[200] = ""; - const char *shortopts, *longopts, *datatype; - - shortopts = table[tabindex]->shortopts; - longopts = table[tabindex]->longopts; - datatype = table[tabindex]->datatype; - arg_cat_optionv(syntax, sizeof(syntax) - 1, shortopts, longopts, datatype, table[tabindex]->flag & ARG_HASOPTVALUE, "|"); - - /* print mandatory options */ - for (i = 0; i < table[tabindex]->mincount; i++) { - arg_dstr_cat(ds, " "); - arg_dstr_cat(ds, syntax); - } - - /* print optional args enclosed in "[..]" */ - switch (table[tabindex]->maxcount - table[tabindex]->mincount) { - case 0: - break; - case 1: - arg_dstr_cat(ds, " ["); - arg_dstr_cat(ds, syntax); - arg_dstr_cat(ds, "]"); - break; - case 2: - arg_dstr_cat(ds, " ["); - arg_dstr_cat(ds, syntax); - arg_dstr_cat(ds, "]"); - arg_dstr_cat(ds, " ["); - arg_dstr_cat(ds, syntax); - arg_dstr_cat(ds, "]"); - break; - default: - arg_dstr_cat(ds, " ["); - arg_dstr_cat(ds, syntax); - arg_dstr_cat(ds, "]..."); - break; - } - } - - if (suffix) { - arg_dstr_cat(ds, (char*)suffix); - } -} - -void arg_print_syntaxv(FILE* fp, void** argtable, const char* suffix) { - arg_dstr_t ds = arg_dstr_create(); - arg_print_syntaxv_ds(ds, argtable, suffix); - fputs(arg_dstr_cstr(ds), fp); - arg_dstr_destroy(ds); -} - -void arg_print_glossary_ds(arg_dstr_t ds, void** argtable, const char* format) { - struct arg_hdr** table = (struct arg_hdr**)argtable; - int tabindex; - - format = format ? format : " %-20s %s\n"; - for (tabindex = 0; !(table[tabindex]->flag & ARG_TERMINATOR); tabindex++) { - if (table[tabindex]->glossary) { - char syntax[200] = ""; - const char* shortopts = table[tabindex]->shortopts; - const char* longopts = table[tabindex]->longopts; - const char* datatype = table[tabindex]->datatype; - const char* glossary = table[tabindex]->glossary; - arg_cat_optionv(syntax, sizeof(syntax) - 1, shortopts, longopts, datatype, table[tabindex]->flag & ARG_HASOPTVALUE, ", "); - arg_dstr_catf(ds, format, syntax, glossary); - } - } -} - -void arg_print_glossary(FILE* fp, void** argtable, const char* format) { - arg_dstr_t ds = arg_dstr_create(); - arg_print_glossary_ds(ds, argtable, format); - fputs(arg_dstr_cstr(ds), fp); - arg_dstr_destroy(ds); -} - -/** - * Print a piece of text formatted, which means in a column with a - * left and a right margin. The lines are wrapped at whitspaces next - * to right margin. The function does not indent the first line, but - * only the following ones. - * - * Example: - * arg_print_formatted( fp, 0, 5, "Some text that doesn't fit." ) - * will result in the following output: - * - * Some - * text - * that - * doesn' - * t fit. - * - * Too long lines will be wrapped in the middle of a word. - * - * arg_print_formatted( fp, 2, 7, "Some text that doesn't fit." ) - * will result in the following output: - * - * Some - * text - * that - * doesn' - * t fit. - * - * As you see, the first line is not indented. This enables output of - * lines, which start in a line where output already happened. - * - * Author: Uli Fouquet - */ -static void arg_print_formatted_ds(arg_dstr_t ds, const unsigned lmargin, const unsigned rmargin, const char* text) { - const unsigned int textlen = (unsigned int)strlen(text); - unsigned int line_start = 0; - unsigned int line_end = textlen; - const unsigned int colwidth = (rmargin - lmargin) + 1; - - assert(strlen(text) < UINT_MAX); - - /* Someone doesn't like us... */ - if (line_end < line_start) { - arg_dstr_catf(ds, "%s\n", text); - } - - while (line_end > line_start) { - /* Eat leading white spaces. This is essential because while - wrapping lines, there will often be a whitespace at beginning - of line. Preserve newlines */ - while (isspace((int)(*(text + line_start))) && *(text + line_start) != '\n') { - line_start++; - } - - /* Find last whitespace, that fits into line */ - if (line_end - line_start > colwidth) { - line_end = line_start + colwidth; - - while ((line_end > line_start) && !isspace((int)(*(text + line_end)))) { - line_end--; - } - - /* If no whitespace could be found, eg. the text is one long word, break the word */ - if (line_end == line_start) { - /* Set line_end to previous value */ - line_end = line_start + colwidth; - } else { - /* Consume trailing spaces, except newlines */ - while ((line_end > line_start) && isspace((int)(*(text + line_end))) && *(text + line_start) != '\n') { - line_end--; - } - - /* Restore the last non-space character */ - line_end++; - } - } - - /* Output line of text */ - while (line_start < line_end) { - char c = *(text + line_start); - - /* If character is newline stop printing, skip this character, as a newline will be printed below. */ - if (c == '\n') { - line_start++; - break; - } - - arg_dstr_catc(ds, c); - line_start++; - } - arg_dstr_cat(ds, "\n"); - - /* Initialize another line */ - if (line_end < textlen) { - unsigned i; - - for (i = 0; i < lmargin; i++) { - arg_dstr_cat(ds, " "); - } - - line_end = textlen; - } - } /* lines of text */ -} - -/** - * Prints the glossary in strict GNU format. - * Differences to arg_print_glossary() are: - * - wraps lines after 80 chars - * - indents lines without shortops - * - does not accept formatstrings - * - * Contributed by Uli Fouquet - */ -void arg_print_glossary_gnu_ds(arg_dstr_t ds, void** argtable) { - struct arg_hdr** table = (struct arg_hdr**)argtable; - int tabindex; - - for (tabindex = 0; !(table[tabindex]->flag & ARG_TERMINATOR); tabindex++) { - if (table[tabindex]->glossary) { - char syntax[200] = ""; - const char* shortopts = table[tabindex]->shortopts; - const char* longopts = table[tabindex]->longopts; - const char* datatype = table[tabindex]->datatype; - const char* glossary = table[tabindex]->glossary; - - if (!shortopts && longopts) { - /* Indent trailing line by 4 spaces... */ - memset(syntax, ' ', 4); - *(syntax + 4) = '\0'; - } - - arg_cat_optionv(syntax, sizeof(syntax) - 1, shortopts, longopts, datatype, table[tabindex]->flag & ARG_HASOPTVALUE, ", "); - - /* If syntax fits not into column, print glossary in new line... */ - if (strlen(syntax) > 25) { - arg_dstr_catf(ds, " %-25s %s\n", syntax, ""); - *syntax = '\0'; - } - - arg_dstr_catf(ds, " %-25s ", syntax); - arg_print_formatted_ds(ds, 28, 79, glossary); - } - } /* for each table entry */ - - arg_dstr_cat(ds, "\n"); -} - -void arg_print_glossary_gnu(FILE* fp, void** argtable) { - arg_dstr_t ds = arg_dstr_create(); - arg_print_glossary_gnu_ds(ds, argtable); - fputs(arg_dstr_cstr(ds), fp); - arg_dstr_destroy(ds); -} - -/** - * Checks the argtable[] array for NULL entries and returns 1 - * if any are found, zero otherwise. - */ -int arg_nullcheck(void** argtable) { - struct arg_hdr** table = (struct arg_hdr**)argtable; - int tabindex; - /*printf("arg_nullcheck(%p)\n",argtable);*/ - - if (!table) - return 1; - - tabindex = 0; - do { - /*printf("argtable[%d]=%p\n",tabindex,argtable[tabindex]);*/ - if (!table[tabindex]) - return 1; - } while (!(table[tabindex++]->flag & ARG_TERMINATOR)); - - return 0; -} - -/* - * arg_free() is deprecated in favour of arg_freetable() due to a flaw in its design. - * The flaw results in memory leak in the (very rare) case that an intermediate - * entry in the argtable array failed its memory allocation while others following - * that entry were still allocated ok. Those subsequent allocations will not be - * deallocated by arg_free(). - * Despite the unlikeliness of the problem occurring, and the even unlikelier event - * that it has any deliterious effect, it is fixed regardless by replacing arg_free() - * with the newer arg_freetable() function. - * We still keep arg_free() for backwards compatibility. - */ -void arg_free(void** argtable) { - struct arg_hdr** table = (struct arg_hdr**)argtable; - int tabindex = 0; - int flag; - /*printf("arg_free(%p)\n",argtable);*/ - do { - /* - if we encounter a NULL entry then somewhat incorrectly we presume - we have come to the end of the array. It isnt strictly true because - an intermediate entry could be NULL with other non-NULL entries to follow. - The subsequent argtable entries would then not be freed as they should. - */ - if (table[tabindex] == NULL) - break; - - flag = table[tabindex]->flag; - xfree(table[tabindex]); - table[tabindex++] = NULL; - - } while (!(flag & ARG_TERMINATOR)); -} - -/* frees each non-NULL element of argtable[], where n is the size of the number of entries in the array */ -void arg_freetable(void** argtable, size_t n) { - struct arg_hdr** table = (struct arg_hdr**)argtable; - size_t tabindex = 0; - /*printf("arg_freetable(%p)\n",argtable);*/ - for (tabindex = 0; tabindex < n; tabindex++) { - if (table[tabindex] == NULL) - continue; - - xfree(table[tabindex]); - table[tabindex] = NULL; - }; -} - -#ifdef _WIN32 -BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { - return TRUE; - UNREFERENCED_PARAMETER(hinstDLL); - UNREFERENCED_PARAMETER(fdwReason); - UNREFERENCED_PARAMETER(lpvReserved); -} -#endif diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/argtable3.h b/parallel/parallel_src/extern/argtable3-3.2.2/argtable3.h deleted file mode 100644 index a4e6bdbd..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/argtable3.h +++ /dev/null @@ -1,273 +0,0 @@ -/******************************************************************************* - * argtable3: Declares the main interfaces of the library - * - * This file is part of the argtable3 library. - * - * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#ifndef ARGTABLE3 -#define ARGTABLE3 - -#include /* FILE */ -#include /* struct tm */ - -#ifdef __cplusplus -extern "C" { -#endif - -#define ARG_REX_ICASE 1 -#define ARG_DSTR_SIZE 200 -#define ARG_CMD_NAME_LEN 100 -#define ARG_CMD_DESCRIPTION_LEN 256 - -#ifndef ARG_REPLACE_GETOPT -#define ARG_REPLACE_GETOPT 1 /* use the embedded getopt as the system getopt(3) */ -#endif /* ARG_REPLACE_GETOPT */ - -/* bit masks for arg_hdr.flag */ -enum { ARG_TERMINATOR = 0x1, ARG_HASVALUE = 0x2, ARG_HASOPTVALUE = 0x4 }; - -#if defined(_WIN32) - #if defined(argtable3_EXPORTS) - #define ARG_EXTERN __declspec(dllexport) - #elif defined(argtable3_IMPORTS) - #define ARG_EXTERN __declspec(dllimport) - #else - #define ARG_EXTERN - #endif -#else - #define ARG_EXTERN -#endif - -typedef struct _internal_arg_dstr* arg_dstr_t; -typedef void* arg_cmd_itr_t; - -typedef void(arg_resetfn)(void* parent); -typedef int(arg_scanfn)(void* parent, const char* argval); -typedef int(arg_checkfn)(void* parent); -typedef void(arg_errorfn)(void* parent, arg_dstr_t ds, int error, const char* argval, const char* progname); -typedef void(arg_dstr_freefn)(char* buf); -typedef int(arg_cmdfn)(int argc, char* argv[], arg_dstr_t res); -typedef int(arg_comparefn)(const void* k1, const void* k2); - -/* - * The arg_hdr struct defines properties that are common to all arg_xxx structs. - * The argtable library requires each arg_xxx struct to have an arg_hdr - * struct as its first data member. - * The argtable library functions then use this data to identify the - * properties of the command line option, such as its option tags, - * datatype string, and glossary strings, and so on. - * Moreover, the arg_hdr struct contains pointers to custom functions that - * are provided by each arg_xxx struct which perform the tasks of parsing - * that particular arg_xxx arguments, performing post-parse checks, and - * reporting errors. - * These functions are private to the individual arg_xxx source code - * and are the pointer to them are initiliased by that arg_xxx struct's - * constructor function. The user could alter them after construction - * if desired, but the original intention is for them to be set by the - * constructor and left unaltered. - */ -typedef struct arg_hdr { - char flag; /* Modifier flags: ARG_TERMINATOR, ARG_HASVALUE. */ - const char* shortopts; /* String defining the short options */ - const char* longopts; /* String defiing the long options */ - const char* datatype; /* Description of the argument data type */ - const char* glossary; /* Description of the option as shown by arg_print_glossary function */ - int mincount; /* Minimum number of occurences of this option accepted */ - int maxcount; /* Maximum number of occurences if this option accepted */ - void* parent; /* Pointer to parent arg_xxx struct */ - arg_resetfn* resetfn; /* Pointer to parent arg_xxx reset function */ - arg_scanfn* scanfn; /* Pointer to parent arg_xxx scan function */ - arg_checkfn* checkfn; /* Pointer to parent arg_xxx check function */ - arg_errorfn* errorfn; /* Pointer to parent arg_xxx error function */ - void* priv; /* Pointer to private header data for use by arg_xxx functions */ -} arg_hdr_t; - -typedef struct arg_rem { - struct arg_hdr hdr; /* The mandatory argtable header struct */ -} arg_rem_t; - -typedef struct arg_lit { - struct arg_hdr hdr; /* The mandatory argtable header struct */ - int count; /* Number of matching command line args */ -} arg_lit_t; - -typedef struct arg_int { - struct arg_hdr hdr; /* The mandatory argtable header struct */ - int count; /* Number of matching command line args */ - int* ival; /* Array of parsed argument values */ -} arg_int_t; - -typedef struct arg_dbl { - struct arg_hdr hdr; /* The mandatory argtable header struct */ - int count; /* Number of matching command line args */ - double* dval; /* Array of parsed argument values */ -} arg_dbl_t; - -typedef struct arg_str { - struct arg_hdr hdr; /* The mandatory argtable header struct */ - int count; /* Number of matching command line args */ - const char** sval; /* Array of parsed argument values */ -} arg_str_t; - -typedef struct arg_rex { - struct arg_hdr hdr; /* The mandatory argtable header struct */ - int count; /* Number of matching command line args */ - const char** sval; /* Array of parsed argument values */ -} arg_rex_t; - -typedef struct arg_file { - struct arg_hdr hdr; /* The mandatory argtable header struct */ - int count; /* Number of matching command line args*/ - const char** filename; /* Array of parsed filenames (eg: /home/foo.bar) */ - const char** basename; /* Array of parsed basenames (eg: foo.bar) */ - const char** extension; /* Array of parsed extensions (eg: .bar) */ -} arg_file_t; - -typedef struct arg_date { - struct arg_hdr hdr; /* The mandatory argtable header struct */ - const char* format; /* strptime format string used to parse the date */ - int count; /* Number of matching command line args */ - struct tm* tmval; /* Array of parsed time values */ -} arg_date_t; - -enum { ARG_ELIMIT = 1, ARG_EMALLOC, ARG_ENOMATCH, ARG_ELONGOPT, ARG_EMISSARG }; -typedef struct arg_end { - struct arg_hdr hdr; /* The mandatory argtable header struct */ - int count; /* Number of errors encountered */ - int* error; /* Array of error codes */ - void** parent; /* Array of pointers to offending arg_xxx struct */ - const char** argval; /* Array of pointers to offending argv[] string */ -} arg_end_t; - -typedef struct arg_cmd_info { - char name[ARG_CMD_NAME_LEN]; - char description[ARG_CMD_DESCRIPTION_LEN]; - arg_cmdfn* proc; -} arg_cmd_info_t; - -/**** arg_xxx constructor functions *********************************/ - -ARG_EXTERN struct arg_rem* arg_rem(const char* datatype, const char* glossary); - -ARG_EXTERN struct arg_lit* arg_lit0(const char* shortopts, const char* longopts, const char* glossary); -ARG_EXTERN struct arg_lit* arg_lit1(const char* shortopts, const char* longopts, const char* glossary); -ARG_EXTERN struct arg_lit* arg_litn(const char* shortopts, const char* longopts, int mincount, int maxcount, const char* glossary); - -ARG_EXTERN struct arg_int* arg_int0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary); -ARG_EXTERN struct arg_int* arg_int1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary); -ARG_EXTERN struct arg_int* arg_intn(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary); - -ARG_EXTERN struct arg_dbl* arg_dbl0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary); -ARG_EXTERN struct arg_dbl* arg_dbl1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary); -ARG_EXTERN struct arg_dbl* arg_dbln(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary); - -ARG_EXTERN struct arg_str* arg_str0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary); -ARG_EXTERN struct arg_str* arg_str1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary); -ARG_EXTERN struct arg_str* arg_strn(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary); - -ARG_EXTERN struct arg_rex* arg_rex0(const char* shortopts, const char* longopts, const char* pattern, const char* datatype, int flags, const char* glossary); -ARG_EXTERN struct arg_rex* arg_rex1(const char* shortopts, const char* longopts, const char* pattern, const char* datatype, int flags, const char* glossary); -ARG_EXTERN struct arg_rex* arg_rexn(const char* shortopts, - const char* longopts, - const char* pattern, - const char* datatype, - int mincount, - int maxcount, - int flags, - const char* glossary); - -ARG_EXTERN struct arg_file* arg_file0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary); -ARG_EXTERN struct arg_file* arg_file1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary); -ARG_EXTERN struct arg_file* arg_filen(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary); - -ARG_EXTERN struct arg_date* arg_date0(const char* shortopts, const char* longopts, const char* format, const char* datatype, const char* glossary); -ARG_EXTERN struct arg_date* arg_date1(const char* shortopts, const char* longopts, const char* format, const char* datatype, const char* glossary); -ARG_EXTERN struct arg_date* arg_daten(const char* shortopts, const char* longopts, const char* format, const char* datatype, int mincount, int maxcount, const char* glossary); - -ARG_EXTERN struct arg_end* arg_end(int maxcount); - -#define ARG_DSTR_STATIC ((arg_dstr_freefn*)0) -#define ARG_DSTR_VOLATILE ((arg_dstr_freefn*)1) -#define ARG_DSTR_DYNAMIC ((arg_dstr_freefn*)3) - -/**** other functions *******************************************/ -ARG_EXTERN int arg_nullcheck(void** argtable); -ARG_EXTERN int arg_parse(int argc, char** argv, void** argtable); -ARG_EXTERN void arg_print_option(FILE* fp, const char* shortopts, const char* longopts, const char* datatype, const char* suffix); -ARG_EXTERN void arg_print_syntax(FILE* fp, void** argtable, const char* suffix); -ARG_EXTERN void arg_print_syntaxv(FILE* fp, void** argtable, const char* suffix); -ARG_EXTERN void arg_print_glossary(FILE* fp, void** argtable, const char* format); -ARG_EXTERN void arg_print_glossary_gnu(FILE* fp, void** argtable); -ARG_EXTERN void arg_print_errors(FILE* fp, struct arg_end* end, const char* progname); -ARG_EXTERN void arg_print_option_ds(arg_dstr_t ds, const char* shortopts, const char* longopts, const char* datatype, const char* suffix); -ARG_EXTERN void arg_print_syntax_ds(arg_dstr_t ds, void** argtable, const char* suffix); -ARG_EXTERN void arg_print_syntaxv_ds(arg_dstr_t ds, void** argtable, const char* suffix); -ARG_EXTERN void arg_print_glossary_ds(arg_dstr_t ds, void** argtable, const char* format); -ARG_EXTERN void arg_print_glossary_gnu_ds(arg_dstr_t ds, void** argtable); -ARG_EXTERN void arg_print_errors_ds(arg_dstr_t ds, struct arg_end* end, const char* progname); -ARG_EXTERN void arg_freetable(void** argtable, size_t n); - -ARG_EXTERN arg_dstr_t arg_dstr_create(void); -ARG_EXTERN void arg_dstr_destroy(arg_dstr_t ds); -ARG_EXTERN void arg_dstr_reset(arg_dstr_t ds); -ARG_EXTERN void arg_dstr_free(arg_dstr_t ds); -ARG_EXTERN void arg_dstr_set(arg_dstr_t ds, char* str, arg_dstr_freefn* free_proc); -ARG_EXTERN void arg_dstr_cat(arg_dstr_t ds, const char* str); -ARG_EXTERN void arg_dstr_catc(arg_dstr_t ds, char c); -ARG_EXTERN void arg_dstr_catf(arg_dstr_t ds, const char* fmt, ...); -ARG_EXTERN char* arg_dstr_cstr(arg_dstr_t ds); - -ARG_EXTERN void arg_cmd_init(void); -ARG_EXTERN void arg_cmd_uninit(void); -ARG_EXTERN void arg_cmd_register(const char* name, arg_cmdfn* proc, const char* description); -ARG_EXTERN void arg_cmd_unregister(const char* name); -ARG_EXTERN int arg_cmd_dispatch(const char* name, int argc, char* argv[], arg_dstr_t res); -ARG_EXTERN unsigned int arg_cmd_count(void); -ARG_EXTERN arg_cmd_info_t* arg_cmd_info(const char* name); -ARG_EXTERN arg_cmd_itr_t arg_cmd_itr_create(void); -ARG_EXTERN void arg_cmd_itr_destroy(arg_cmd_itr_t itr); -ARG_EXTERN int arg_cmd_itr_advance(arg_cmd_itr_t itr); -ARG_EXTERN char* arg_cmd_itr_key(arg_cmd_itr_t itr); -ARG_EXTERN arg_cmd_info_t* arg_cmd_itr_value(arg_cmd_itr_t itr); -ARG_EXTERN int arg_cmd_itr_search(arg_cmd_itr_t itr, void* k); -ARG_EXTERN void arg_mgsort(void* data, int size, int esize, int i, int k, arg_comparefn* comparefn); -ARG_EXTERN void arg_make_get_help_msg(arg_dstr_t res); -ARG_EXTERN void arg_make_help_msg(arg_dstr_t ds, char* cmd_name, void** argtable); -ARG_EXTERN void arg_make_syntax_err_msg(arg_dstr_t ds, void** argtable, struct arg_end* end); -ARG_EXTERN int arg_make_syntax_err_help_msg(arg_dstr_t ds, char* name, int help, int nerrors, void** argtable, struct arg_end* end, int* exitcode); -ARG_EXTERN void arg_set_module_name(const char* name); -ARG_EXTERN void arg_set_module_version(int major, int minor, int patch, const char* tag); - -/**** deprecated functions, for back-compatibility only ********/ -ARG_EXTERN void arg_free(void** argtable); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/examples/CMakeLists.txt b/parallel/parallel_src/extern/argtable3-3.2.2/examples/CMakeLists.txt deleted file mode 100644 index 67e22218..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/examples/CMakeLists.txt +++ /dev/null @@ -1,54 +0,0 @@ -################################################################################ -# This file is part of the argtable3 library. -# -# Copyright (C) 2016-2021 Tom G. Huang -# -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of STEWART HEITMANN nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, -# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -################################################################################ - -if(ARGTABLE3_ENABLE_ARG_REX_DEBUG) - add_definitions(-DARG_REX_DEBUG) -endif() - -if(NOT ARGTABLE3_REPLACE_GETOPT) - add_definitions(-DARG_REPLACE_GETOPT=0) -endif() - -if(ARGTABLE3_LONG_ONLY) - add_definitions(-DARG_LONG_ONLY) -endif() - -file(GLOB EXAMPLES_SOURCES RELATIVE ${PROJECT_SOURCE_DIR}/examples *.c) - -if(UNIX) - set(ARGTABLE3_EXTRA_LIBS m) -endif() - -foreach(examples_src ${EXAMPLES_SOURCES}) - string(REPLACE ".c" "" examplename ${examples_src}) - add_executable(${examplename} ${PROJECT_SOURCE_DIR}/examples/${examples_src}) - target_include_directories(${examplename} PRIVATE ${PROJECT_SOURCE_DIR}/src) - target_link_libraries(${examplename} argtable3 ${ARGTABLE3_EXTRA_LIBS}) -endforeach() diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/examples/echo.c b/parallel/parallel_src/extern/argtable3-3.2.2/examples/echo.c deleted file mode 100644 index fb13f3b4..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/examples/echo.c +++ /dev/null @@ -1,128 +0,0 @@ -/******************************************************************************* - * Example source code for using the argtable3 library to implement: - * - * echo [-neE] [--help] [--version] [STRING]... - * - * This file is part of the argtable3 library. - * - * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include "argtable3.h" - -/* Here we only approximate the echo functionality */ -void mymain(int n, int e, int E, const char** strings, int nstrings) - { - int j; - - printf("option -n = %s\n", ((n)?"YES":"NO")); - printf("option -e = %s\n", ((e)?"YES":"NO")); - printf("option -E = %s\n", ((E)?"YES":"NO")); - for (j=0; js"); - struct arg_lit *help = arg_lit0(NULL,"help", "print this help and exit"); - struct arg_lit *vers = arg_lit0(NULL,"version", "print version information and exit"); - struct arg_str *strs = arg_strn(NULL,NULL,"STRING",0,argc+2,NULL); - struct arg_end *end = arg_end(20); - void* argtable[] = {n,e,E,help,vers,strs,end}; - const char* progname = "echo"; - int exitcode=0; - int nerrors; - - /* verify the argtable[] entries were allocated sucessfully */ - if (arg_nullcheck(argtable) != 0) - { - /* NULL entries were detected, some allocations must have failed */ - printf("%s: insufficient memory\n",progname); - exitcode=1; - goto exit; - } - - /* Parse the command line as defined by argtable[] */ - nerrors = arg_parse(argc,argv,argtable); - - /* special case: '--help' takes precedence over error reporting */ - if (help->count > 0) - { - printf("Usage: %s", progname); - arg_print_syntax(stdout,argtable,"\n"); - printf("Echo the STRINGs to standard output.\n\n"); - arg_print_glossary(stdout,argtable," %-10s %s\n"); - printf("\nWithout -E, the following sequences are recognized and interpolated:\n\n" - " \\NNN the character whose ASCII code is NNN (octal)\n" - " \\\\ backslash\n" - " \\a alert (BEL)\n" - " \\b backspace\n" - " \\c suppress trailing newline\n" - " \\f form feed\n" - " \\n new line\n" - " \\r carriage return\n" - " \\t horizontal tab\n" - " \\v vertical tab\n\n" - "Report bugs to .\n"); - exitcode=0; - goto exit; - } - - /* special case: '--version' takes precedence error reporting */ - if (vers->count > 0) - { - printf("'%s' example program for the \"argtable\" command line argument parser.\n",progname); - printf("September 2003, Stewart Heitmann\n"); - exitcode=0; - goto exit; - } - - /* If the parser returned any errors then display them and exit */ - if (nerrors > 0) - { - /* Display the error details contained in the arg_end struct.*/ - arg_print_errors(stdout,end,progname); - printf("Try '%s --help' for more information.\n",progname); - exitcode=1; - goto exit; - } - - /* Command line parsing is complete, do the main processing */ - mymain(n->count, e->count, E->count, strs->sval, strs->count); - - exit: - /* deallocate each non-null entry in argtable[] */ - arg_freetable(argtable,sizeof(argtable)/sizeof(argtable[0])); - - return exitcode; - } diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/examples/ls.c b/parallel/parallel_src/extern/argtable3-3.2.2/examples/ls.c deleted file mode 100644 index c7317a88..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/examples/ls.c +++ /dev/null @@ -1,326 +0,0 @@ -/******************************************************************************* - * Example source code for using the argtable3 library to implement: - * - * ls [-aAbBcCdDfFgGhHiklLmnNopqQrRsStuUvxX1] [--author] - * [--block-size=SIZE] [--color=[WHEN]] [--format=WORD] [--full-time] - * [--si] [--dereference-command-line-symlink-to-dir] [--indicator-style=WORD] - * [-I PATTERN] [--show-control-chars] [--quoting-style=WORD] [--sort=WORD] - * [--time=WORD] [--time-style=STYLE] [-T COLS] [-w COLS] [--help] - * [--version] [FILE]... - * - * This file is part of the argtable3 library. - * - * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include "argtable3.h" - -/* These variables hold the values parsed from the comand line by arg_parse() */ -struct arg_lit *a, *A, *author, *b, *B, *c, *C, *d, *D, *f, *F, *fulltime; -struct arg_lit *g, *G, *h, *H, *si, *deref, *i, *k, *l, *L, *m, *n, *N, *o, *p; -struct arg_lit *q, *shcont, *Q, *r, *R, *s, *S, *t, *u, *U, *v, *x, *X, *one; -struct arg_lit *help, *version; -struct arg_int *blocksize, *T, *w; -struct arg_str *color, *format, *indic, *I, *Qstyle, *sort, *Time, *timesty; -struct arg_file *files; -struct arg_end *end; - -/* Here we simply echo the command line option values as a demonstration. */ -/* In a real program, this is where we would perform the main processing. */ -int mymain(void) - { - int j; - - if (a->count > 0) - printf("a=YES\n"); - if (A->count > 0) - printf("A=YES\n"); - if (author->count > 0) - printf("author=YES\n"); - if (b->count > 0) - printf("b=YES\n"); - if (blocksize->count > 0) - printf("blocksize=%d\n",blocksize->count); - if (B->count > 0) - printf("B=YES\n"); - if (c->count > 0) - printf("c=YES\n"); - if (C->count > 0) - printf("C=YES\n"); - if (color->count > 0) - printf("color=%s\n",color->sval[0]); - if (d->count > 0) - printf("d=YES\n"); - if (D->count > 0) - printf("D=YES\n"); - if (f->count > 0) - printf("f=YES\n"); - if (F->count > 0) - printf("F=YES\n"); - if (format->count > 0) - printf("format=%s\n",format->sval[0]); - if (fulltime->count > 0) - printf("fulltime=YES\n"); - if (g->count > 0) - printf("g=YES\n"); - if (G->count > 0) - printf("G=YES\n"); - if (h->count > 0) - printf("h=YES\n"); - if (si->count > 0) - printf("si=YES\n"); - if (H->count > 0) - printf("H=YES\n"); - if (deref->count > 0) - printf("deref=YES\n"); - if (indic->count > 0) - printf("indic=%s\n",indic->sval[0]); - if (i->count > 0) - printf("i=YES\n"); - if (I->count > 0) - printf("I=%s\n",I->sval[0]); - if (k->count > 0) - printf("k=YES\n"); - if (l->count > 0) - printf("l=YES\n"); - if (L->count > 0) - printf("L=YES\n"); - if (m->count > 0) - printf("m=YES\n"); - if (n->count > 0) - printf("n=YES\n"); - if (N->count > 0) - printf("N=YES\n"); - if (o->count > 0) - printf("o=YES\n"); - if (p->count > 0) - printf("p=YES\n"); - if (q->count > 0) - printf("q=YES\n"); - if (shcont->count > 0) - printf("shcont=YES\n"); - if (Q->count > 0) - printf("Q=YES\n"); - if (Qstyle->count > 0) - printf("Qstyle=%s\n",Qstyle->sval[0]); - if (r->count > 0) - printf("r=YES\n"); - if (R->count > 0) - printf("R=YES\n"); - if (s->count > 0) - printf("s=YES\n"); - if (S->count > 0) - printf("S=YES\n"); - if (sort->count > 0) - printf("sort=%s\n",sort->sval[0]); - if (Time->count > 0) - printf("time=%s\n",Time->sval[0]); - if (timesty->count > 0) - printf("timesty=%s\n",timesty->sval[0]); - if (t->count > 0) - printf("t=YES\n"); - if (T->count > 0) - printf("T=%d\n",T->ival[0]); - if (u->count > 0) - printf("u=YES\n"); - if (U->count > 0) - printf("U=YES\n"); - if (v->count > 0) - printf("v=YES\n"); - if (w->count > 0) - printf("w=%d\n",w->ival[0]); - if (x->count > 0) - printf("x=YES\n"); - if (X->count > 0) - printf("X=YES\n"); - if (one->count > 0) - printf("1=YES\n"); - - /* print the filenames */ - for (j=0; jcount; j++) - printf("filename[%d] = \"%s\"\n", j, files->filename[j]); - - return 0; - } - - -int main(int argc, char **argv) - { - /* The argtable[] entries define the command line options */ - void *argtable[] = { - a = arg_lit0("a", "all", "do not hide entries starting with ."), - A = arg_lit0("A", "almost-all", "do not list implied . and .."), - author = arg_lit0(NULL,"author", "print the author of each file"), - b = arg_lit0("b", "escape", "print octal escapes for nongraphic characters"), - blocksize = arg_int0(NULL,"block-size","SIZE", "use SIZE-byte blocks"), - B = arg_lit0("B", "ignore-backups", "do not list implied entries ending with ~"), - c = arg_lit0("c", NULL, "with -lt: sort by, and show, ctime (time of last"), - arg_rem(NULL, " modification of file status information)"), - arg_rem(NULL, " with -l: show ctime and sort by name"), - arg_rem(NULL, " otherwise: sort by ctime"), - C = arg_lit0("C", NULL, "list entries by columns"), - color = arg_str0(NULL,"color","WHEN", "control whether color is used to distinguish file"), - arg_rem(NULL, " types. WHEN may be `never', `always', or `auto'"), - d = arg_lit0("d", "directory", "list directory entries instead of contents,"), - arg_rem(NULL, " and do not dereference symbolic links"), - D = arg_lit0("D", "dired", "generate output designed for Emacs' dired mode"), - f = arg_lit0("f", NULL, "do not sort, enable -aU, disable -lst"), - F = arg_lit0("F", "classify", "append indicator (one of */=@|) to entries"), - format = arg_str0(NULL,"format","WORD", "across -x, commas -m, horizontal -x, long -l,"), - arg_rem (NULL, " single-column -1, verbose -l, vertical -C"), - fulltime = arg_lit0(NULL,"full-time", "like -l --time-style=full-iso"), - g = arg_lit0("g", NULL, "like -l, but do not list owner"), - G = arg_lit0("G", "no-group", "inhibit display of group information"), - h = arg_lit0("h", "human-readable", "print sizes in human readable format (e.g., 1K 234M 2G)"), - si = arg_lit0(NULL,"si", "likewise, but use powers of 1000 not 1024"), - H = arg_lit0("H", "dereference-command-line","follow symbolic links listed on the command line"), - deref = arg_lit0(NULL,"dereference-command-line-symlink-to-dir","follow each command line symbolic link"), - arg_rem(NULL, " that points to a directory"), - indic = arg_str0(NULL,"indicator-style","WORD","append indicator with style WORD to entry names:"), - arg_rem (NULL, " none (default), classify (-F), file-type (-p)"), - i = arg_lit0("i", "inode", "print index number of each file"), - I = arg_str0("I", "ignore","PATTERN", "do not list implied entries matching shell PATTERN"), - k = arg_lit0("k", NULL, "like --block-size=1K"), - l = arg_lit0("l", NULL, "use a long listing format"), - L = arg_lit0("L", "dereference", "when showing file information for a symbolic"), - arg_rem (NULL, " link, show information for the file the link"), - arg_rem (NULL, " references rather than for the link itself"), - m = arg_lit0("m", NULL, "fill width with a comma separated list of entries"), - n = arg_lit0("n", "numeric-uid-gid", "like -l, but list numeric UIDs and GIDs"), - N = arg_lit0("N", "literal", "print raw entry names (don't treat e.g. control"), - arg_rem (NULL, " characters specially)"), - o = arg_lit0("o", NULL, "like -l, but do not list group information"), - p = arg_lit0("p", "file-type", "append indicator (one of /=@|) to entries"), - q = arg_lit0("q", "hide-control-chars", "print ? instead of non graphic characters"), - shcont = arg_lit0(NULL,"show-control-chars", "show non graphic characters as-is (default"), - arg_rem (NULL, "unless program is `ls' and output is a terminal)"), - Q = arg_lit0("Q", "quote-name", "enclose entry names in double quotes"), - Qstyle = arg_str0(NULL,"quoting-style","WORD","use quoting style WORD for entry names:"), - arg_rem (NULL, " literal, locale, shell, shell-always, c, escape"), - r = arg_lit0("r", "reverse", "reverse order while sorting"), - R = arg_lit0("R", "recursive", "list subdirectories recursively"), - s = arg_lit0("s", "size", "print size of each file, in blocks"), - S = arg_lit0("S", NULL, "sort by file size"), - sort = arg_str0(NULL,"sort","WORD", "extension -X, none -U, size -S, time -t, version -v,"), - arg_rem (NULL, "status -c, time -t, atime -u, access -u, use -u"), - Time = arg_str0(NULL,"time","WORD", "show time as WORD instead of modification time:"), - arg_rem (NULL, " atime, access, use, ctime or status; use"), - arg_rem (NULL, " specified time as sort key if --sort=time"), - timesty = arg_str0(NULL, "time-style","STYLE", "show times using style STYLE:"), - arg_rem (NULL, " full-iso, long-iso, iso, locale, +FORMAT"), - arg_rem (NULL, "FORMAT is interpreted like `date'; if FORMAT is"), - arg_rem (NULL, "FORMAT1FORMAT2, FORMAT1 applies to"), - arg_rem (NULL, "non-recent files and FORMAT2 to recent files;"), - arg_rem (NULL, "if STYLE is prefixed with `posix-', STYLE"), - arg_rem (NULL, "takes effect only outside the POSIX locale"), - t = arg_lit0("t", NULL, "sort by modification time"), - T = arg_int0("T", "tabsize", "COLS", "assume tab stops at each COLS instead of 8"), - u = arg_lit0("u", NULL, "with -lt: sort by, and show, access time"), - arg_rem (NULL, " with -l: show access time and sort by name"), - arg_rem (NULL, " otherwise: sort by access time"), - U = arg_lit0("U", NULL, "do not sort; list entries in directory order"), - v = arg_lit0("v", NULL, "sort by version"), - w = arg_int0("w", "width", "COLS", "assume screen width instead of current value"), - x = arg_lit0("x", NULL, "list entries by lines instead of by columns"), - X = arg_lit0("X", NULL, "sort alphabetically by entry extension"), - one = arg_lit0("1", NULL, "list one file per line"), - help = arg_lit0(NULL,"help", "display this help and exit"), - version = arg_lit0(NULL,"version", "display version information and exit"), - files = arg_filen(NULL, NULL, "FILE", 0, argc+2, NULL), - end = arg_end(20), - }; - const char *progname = "ls"; - int exitcode=0; - int nerrors; - - /* verify the argtable[] entries were allocated sucessfully */ - if (arg_nullcheck(argtable) != 0) - { - /* NULL entries were detected, some allocations must have failed */ - printf("%s: insufficient memory\n",progname); - exitcode=1; - goto exit; - } - - /* allow optional argument values for --color */ - /* and set the default value to "always" */ - color->hdr.flag |= ARG_HASOPTVALUE; - color->sval[0] = "always"; - - /* Parse the command line as defined by argtable[] */ - nerrors = arg_parse(argc,argv,argtable); - - /* special case: '--help' takes precedence over error reporting */ - if (help->count > 0) - { - printf("Usage: %s", progname); - arg_print_syntax(stdout,argtable,"\n"); - printf("List information about the FILE(s) (the current directory by default).\n"); - printf("Sort entries alphabetically if none of -cftuSUX nor --sort.\n\n"); - arg_print_glossary(stdout,argtable," %-25s %s\n"); - printf("\nSIZE may be (or may be an integer optionally followed by) one of following:\n" - "kB 1000, K 1024, MB 1,000,000, M 1,048,576, and so on for G, T, P, E, Z, Y.\n\n" - "By default, color is not used to distinguish types of files. That is\n" - "equivalent to using --color=none. Using the --color option without the\n" - "optional WHEN argument is equivalent to using --color=always. With\n" - "--color=auto, color codes are output only if standard output is connected\n" - "to a terminal (tty).\n\n" - "Report bugs to .\n"); - exitcode=0; - goto exit; - } - - /* special case: '--version' takes precedence error reporting */ - if (version->count > 0) - { - printf("'%s' example program for the \"argtable\" command line argument parser.\n",progname); - printf("September 2003, Stewart Heitmann\n"); - exitcode=0; - goto exit; - } - - /* If the parser returned any errors then display them and exit */ - if (nerrors > 0) - { - /* Display the error details contained in the arg_end struct.*/ - arg_print_errors(stdout,end,progname); - printf("Try '%s --help' for more information.\n",progname); - exitcode=1; - goto exit; - } - - /* Command line parsing is complete, do the main processing */ - exitcode = mymain(); - -exit: - /* deallocate each non-null entry in argtable[] */ - arg_freetable(argtable,sizeof(argtable)/sizeof(argtable[0])); - - return exitcode; - } - - diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/examples/multisyntax.c b/parallel/parallel_src/extern/argtable3-3.2.2/examples/multisyntax.c deleted file mode 100644 index 45b2cd29..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/examples/multisyntax.c +++ /dev/null @@ -1,239 +0,0 @@ -/******************************************************************************* - * Example source code for using the argtable3 library to implement - * a multi-syntax command line argument program - * - * usage 1: multisyntax [-nvR] insert []... [-o ] - * usage 2: multisyntax [-nv] remove - * usage 3: multisyntax [-v] search [-o ] - * usage 4: multisyntax [--help] [--version] - * - * This file is part of the argtable3 library. - * - * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include "argtable3.h" - -#define REG_EXTENDED 1 -#define REG_ICASE (REG_EXTENDED << 1) - -/* mymain1 implements the actions for syntax 1 */ -int mymain1(int n, int v, int R, const char *outfile, - const char **infiles, int ninfiles) - { - int i; - printf("syntax 1 matched OK:\n"); - printf("n=%d\n", n); - printf("v=%d\n", v); - printf("R=%d\n", R); - printf("outfile=\"%s\"\n", outfile); - for (i=0; i [file]... -o */ - struct arg_rex *cmd1 = arg_rex1(NULL, NULL, "insert", NULL, REG_ICASE, NULL); - struct arg_lit *noact1 = arg_lit0("n", NULL, "take no action"); - struct arg_lit *verbose1 = arg_lit0("v", "verbose", "verbose messages"); - struct arg_lit *recurse1 = arg_lit0("R", NULL, "recurse through subdirectories"); - struct arg_file *infiles1 = arg_filen(NULL, NULL, NULL, 1,argc+2, "input file(s)"); - struct arg_file *outfile1 = arg_file0("o", NULL, "", "output file (default is \"-\")"); - struct arg_end *end1 = arg_end(20); - void* argtable1[] = {cmd1,noact1,verbose1,recurse1,infiles1,outfile1,end1}; - int nerrors1; - - /* SYNTAX 2: remove [-nv] */ - struct arg_rex *cmd2 = arg_rex1(NULL, NULL, "remove", NULL, REG_ICASE, NULL); - struct arg_lit *noact2 = arg_lit0("n", NULL, NULL); - struct arg_lit *verbose2 = arg_lit0("v", "verbose", NULL); - struct arg_file *infiles2 = arg_file1(NULL, NULL, NULL, NULL); - struct arg_end *end2 = arg_end(20); - void* argtable2[] = {cmd2,noact2,verbose2,infiles2,end2}; - int nerrors2; - - /* SYNTAX 3: search [-v] [-o ] [--help] [--version] */ - struct arg_rex *cmd3 = arg_rex1(NULL, NULL, "search", NULL, REG_ICASE, NULL); - struct arg_lit *verbose3 = arg_lit0("v", "verbose", NULL); - struct arg_str *pattern3 = arg_str1(NULL, NULL, "", "search string"); - struct arg_file *outfile3 = arg_file0("o", NULL, "", NULL); - struct arg_end *end3 = arg_end(20); - void* argtable3[] = {cmd3,verbose3,pattern3,outfile3,end3}; - int nerrors3; - - /* SYNTAX 4: [-help] [-version] */ - struct arg_lit *help4 = arg_lit0(NULL,"help", "print this help and exit"); - struct arg_lit *version4 = arg_lit0(NULL,"version", "print version information and exit"); - struct arg_end *end4 = arg_end(20); - void* argtable4[] = {help4,version4,end4}; - int nerrors4; - - const char* progname = "multisyntax"; - int exitcode=0; - - /* verify all argtable[] entries were allocated sucessfully */ - if (arg_nullcheck(argtable1)!=0 || - arg_nullcheck(argtable2)!=0 || - arg_nullcheck(argtable3)!=0 || - arg_nullcheck(argtable4)!=0 ) - { - /* NULL entries were detected, some allocations must have failed */ - printf("%s: insufficient memory\n",progname); - exitcode=1; - goto exit; - } - - /* set any command line default values prior to parsing */ - outfile1->filename[0]="-"; - outfile3->filename[0]="-"; - - /* Above we defined a separate argtable for each possible command line syntax */ - /* and here we parse each one in turn to see if any of them are successful */ - nerrors1 = arg_parse(argc,argv,argtable1); - nerrors2 = arg_parse(argc,argv,argtable2); - nerrors3 = arg_parse(argc,argv,argtable3); - nerrors4 = arg_parse(argc,argv,argtable4); - - /* Execute the appropriate main routine for the matching command line syntax */ - /* In this example program our alternate command line syntaxes are mutually */ - /* exclusive, so we know in advance that only one of them can be successful. */ - if (nerrors1==0) - exitcode = mymain1(noact1->count, verbose1->count, recurse1->count, - outfile1->filename[0], infiles1->filename, infiles1->count); - else if (nerrors2==0) - exitcode = mymain2(noact2->count, verbose2->count, infiles2->filename[0]); - else if (nerrors3==0) - exitcode = mymain3(verbose3->count, pattern3->sval[0], outfile3->filename[0]); - else if (nerrors4==0) - exitcode = mymain4(help4->count, version4->count, progname, - argtable1, argtable2, argtable3, argtable4); - else - { - /* We get here if the command line matched none of the possible syntaxes */ - if (cmd1->count > 0) - { - /* here the cmd1 argument was correct, so presume syntax 1 was intended target */ - arg_print_errors(stdout,end1,progname); - printf("usage: %s ", progname); - arg_print_syntax(stdout,argtable1,"\n"); - } - else if (cmd2->count > 0) - { - /* here the cmd2 argument was correct, so presume syntax 2 was intended target */ - arg_print_errors(stdout,end2,progname); - printf("usage: %s ", progname); - arg_print_syntax(stdout,argtable2,"\n"); - } - else if (cmd3->count > 0) - { - /* here the cmd3 argument was correct, so presume syntax 3 was intended target */ - arg_print_errors(stdout,end3,progname); - printf("usage: %s ", progname); - arg_print_syntax(stdout,argtable3,"\n"); - } - else - { - /* no correct cmd literals were given, so we cant presume which syntax was intended */ - printf("%s: missing command.\n",progname); - printf("usage 1: %s ", progname); arg_print_syntax(stdout,argtable1,"\n"); - printf("usage 2: %s ", progname); arg_print_syntax(stdout,argtable2,"\n"); - printf("usage 3: %s ", progname); arg_print_syntax(stdout,argtable3,"\n"); - printf("usage 4: %s", progname); arg_print_syntax(stdout,argtable4,"\n"); - } - } - -exit: - /* deallocate each non-null entry in each argtable */ - arg_freetable(argtable1,sizeof(argtable1)/sizeof(argtable1[0])); - arg_freetable(argtable2,sizeof(argtable2)/sizeof(argtable2[0])); - arg_freetable(argtable3,sizeof(argtable3)/sizeof(argtable3[0])); - arg_freetable(argtable4,sizeof(argtable4)/sizeof(argtable4[0])); - - return exitcode; - } diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/examples/mv.c b/parallel/parallel_src/extern/argtable3-3.2.2/examples/mv.c deleted file mode 100644 index 85048223..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/examples/mv.c +++ /dev/null @@ -1,179 +0,0 @@ -/******************************************************************************* - * Example source code for using the argtable3 library to implement: - * - * mv [-bfiuv] [--backup=[CONTROL]] [--reply={yes,no,query}] - * [--strip-trailing-slashes] [-S SUFFIX] [--target-directory=DIRECTORY] - * [--help] [--version] SOURCE [SOURCE]... DEST|DIRECTORY - * - * This file is part of the argtable3 library. - * - * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include "argtable3.h" - -int mymain(const char *backup_control, - int backup, - int force, - int interactive, - const char *reply, - int strip_trailing_slashes, - const char *suffix, - const char *targetdir, - int update, - int verbose, - const char **files, - int nfiles) - { - int j; - - /* if verbose option was given then display all option settings */ - if (verbose) - { - printf("backup = %s\n", ((backup)?"YES":"NO")); - printf("backup CONTROL = %s\n", backup_control); - printf("force = %s\n", ((force)?"YES":"NO")); - printf("interactive mode = %s\n", ((interactive)?"YES":"NO")); - printf("reply = %s\n", reply); - printf("strip-trailing-slashes = %s\n", ((strip_trailing_slashes)?"YES":"NO")); - printf("suffix = %s\n", suffix); - printf("target-directory = %s\n", targetdir); - printf("update = %s\n", ((update)?"YES":"NO")); - printf("verbose = %s\n", ((verbose)?"YES":"NO")); - } - - /* print the source filenames */ - for (j=0; jsval[0] = "existing"; /* --backup={none,off,numbered,t,existing,nil,simple,never} */ - suffix->sval[0] = "~"; /* --suffix=~ */ - reply->sval[0] = "query"; /* --reply={yes,no,query} */ - targetd->sval[0] = NULL; - - /* Parse the command line as defined by argtable[] */ - nerrors = arg_parse(argc,argv,argtable); - - /* special case: '--help' takes precedence over error reporting */ - if (help->count > 0) - { - printf("Usage: %s", progname); - arg_print_syntax(stdout,argtable,"\n"); - printf("Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.\n\n"); - arg_print_glossary(stdout,argtable," %-30s %s\n"); - printf("\nThe backup suffix is \"~\", unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n" - "The version control method may be selected via the --backup option or through\n" - "the VERSION_CONTROL environment variable. Here are the values:\n\n" - " none, off never make backups (even if --backup is given)\n" - " numbered, t make numbered backups\n" - " existing, nil numbered if numbered backups exist, simple otherwise\n" - " simple, never always make simple backups\n\n" - "Report bugs to .\n"); - exitcode=0; - goto exit; - } - - /* special case: '--version' takes precedence error reporting */ - if (version->count > 0) - { - printf("'%s' example program for the \"argtable\" command line argument parser.\n",progname); - printf("September 2003, Stewart Heitmann\n"); - exitcode=0; - goto exit; - } - - /* If the parser returned any errors then display them and exit */ - if (nerrors > 0) - { - /* Display the error details contained in the arg_end struct.*/ - arg_print_errors(stdout,end,progname); - printf("Try '%s --help' for more information.\n",progname); - exitcode=1; - goto exit; - } - - /* Command line parsing is complete, do the main processing */ - exitcode = mymain(backupc->sval[0], - backup->count, - force->count, - interact->count, - reply->sval[0], - strpslsh->count, - suffix->sval[0], - targetd->sval[0], - update->count, - verbose->count, - files->filename, - files->count); - -exit: - /* deallocate each non-null entry in argtable[] */ - arg_freetable(argtable,sizeof(argtable)/sizeof(argtable[0])); - - return exitcode; - } diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/examples/myprog.c b/parallel/parallel_src/extern/argtable3-3.2.2/examples/myprog.c deleted file mode 100644 index b705cf44..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/examples/myprog.c +++ /dev/null @@ -1,147 +0,0 @@ -/******************************************************************************* - * Example source code for using the argtable3 library to implement: - * - * myprog [-lRv] [-k ] [-D MACRO]... [-o ] [--help] - * [--version] []... - * - * This file is part of the argtable3 library. - * - * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include - -int mymain(int l, int R, int k, - const char **defines, int ndefines, - const char *outfile, - int v, - const char **infiles, int ninfiles) - { - int i; - - if (l>0) printf("list files (-l)\n"); - if (R>0) printf("recurse through directories (-R)\n"); - if (v>0) printf("verbose is enabled (-v)\n"); - printf("scalar k=%d\n",k); - printf("output is \"%s\"\n", outfile); - - for (i=0; i", "output file (default is \"-\")"); - struct arg_lit *verbose = arg_lit0("v","verbose,debug", "verbose messages"); - struct arg_lit *help = arg_lit0(NULL,"help", "print this help and exit"); - struct arg_lit *version = arg_lit0(NULL,"version", "print version information and exit"); - struct arg_file *infiles = arg_filen(NULL,NULL,NULL,1,argc+2, "input file(s)"); - struct arg_end *end = arg_end(20); - void* argtable[] = {list,recurse,repeat,defines,outfile,verbose,help,version,infiles,end}; - const char* progname = "myprog"; - int nerrors; - int exitcode=0; - - /* verify the argtable[] entries were allocated sucessfully */ - if (arg_nullcheck(argtable) != 0) - { - /* NULL entries were detected, some allocations must have failed */ - printf("%s: insufficient memory\n",progname); - exitcode=1; - goto exit; - } - - /* set any command line default values prior to parsing */ - repeat->ival[0]=3; - outfile->filename[0]="-"; - - /* Parse the command line as defined by argtable[] */ - nerrors = arg_parse(argc,argv,argtable); - - /* special case: '--help' takes precedence over error reporting */ - if (help->count > 0) - { - printf("Usage: %s", progname); - arg_print_syntax(stdout,argtable,"\n"); - printf("This program demonstrates the use of the argtable2 library\n"); - printf("for parsing command line arguments. Argtable accepts integers\n"); - printf("in decimal (123), hexadecimal (0xff), octal (0o123) and binary\n"); - printf("(0b101101) formats. Suffixes KB, MB and GB are also accepted.\n"); - arg_print_glossary(stdout,argtable," %-25s %s\n"); - exitcode=0; - goto exit; - } - - /* special case: '--version' takes precedence error reporting */ - if (version->count > 0) - { - printf("'%s' example program for the \"argtable\" command line argument parser.\n",progname); - printf("September 2003, Stewart Heitmann\n"); - exitcode=0; - goto exit; - } - - /* If the parser returned any errors then display them and exit */ - if (nerrors > 0) - { - /* Display the error details contained in the arg_end struct.*/ - arg_print_errors(stdout,end,progname); - printf("Try '%s --help' for more information.\n",progname); - exitcode=1; - goto exit; - } - - /* special case: uname with no command line options induces brief help */ - if (argc==1) - { - printf("Try '%s --help' for more information.\n",progname); - exitcode=0; - goto exit; - } - - /* normal case: take the command line options at face value */ - exitcode = mymain(list->count, recurse->count, repeat->ival[0], - defines->sval, defines->count, - outfile->filename[0], verbose->count, - infiles->filename, infiles->count); - - exit: - /* deallocate each non-null entry in argtable[] */ - arg_freetable(argtable,sizeof(argtable)/sizeof(argtable[0])); - - return exitcode; - } diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/examples/myprog_C89.c b/parallel/parallel_src/extern/argtable3-3.2.2/examples/myprog_C89.c deleted file mode 100644 index 21d03883..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/examples/myprog_C89.c +++ /dev/null @@ -1,165 +0,0 @@ -/******************************************************************************* - * This example source code is an alternate version of myprog.c - * that adheres to ansi C89 standards rather than ansi C99. - * The only difference being that C89 does not permit the argtable array - * to be statically initialized with the contents of variables set at - * runtime whereas C99 does. - * Hence we cannot declare and initialize the argtable array in one declaration - * as - * void* argtable[] = {list, recurse, repeat, defines, outfile, verbose, - * help, version, infiles, end}; - * Instead, we must declare - * void* argtable[10]; - * and initialize the contents of the array separately. - * - * This file is part of the argtable3 library. - * - * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include "argtable3.h" - -int mymain(int l, int R, int k, - const char **defines, int ndefines, - const char *outfile, - int v, - const char **infiles, int ninfiles) - { - int i; - - if (l>0) printf("list files (-l)\n"); - if (R>0) printf("recurse through directories (-R)\n"); - if (v>0) printf("verbose is enabled (-v)\n"); - printf("scalar k=%d\n",k); - printf("output is \"%s\"\n", outfile); - - for (i=0; i", "output file (default is \"-\")"); - struct arg_lit *verbose = arg_lit0("v","verbose,debug", "verbose messages"); - struct arg_lit *help = arg_lit0(NULL,"help", "print this help and exit"); - struct arg_lit *version = arg_lit0(NULL,"version", "print version information and exit"); - struct arg_file *infiles = arg_filen(NULL,NULL,NULL,1,argc+2, "input file(s)"); - struct arg_end *end = arg_end(20); - void* argtable[10]; - const char* progname = "myprog_C89"; - int nerrors; - int exitcode=0; - - /* initialize the argtable array with ptrs to the arg_xxx structures constructed above */ - argtable[0] = list; - argtable[1] = recurse; - argtable[2] = repeat; - argtable[3] = defines; - argtable[4] = outfile; - argtable[5] = verbose; - argtable[6] = help; - argtable[7] = version; - argtable[8] = infiles; - argtable[9] = end; - - /* verify the argtable[] entries were allocated sucessfully */ - if (arg_nullcheck(argtable) != 0) - { - /* NULL entries were detected, some allocations must have failed */ - printf("%s: insufficient memory\n",progname); - exitcode=1; - goto exit; - } - - /* set any command line default values prior to parsing */ - repeat->ival[0]=3; - outfile->filename[0]="-"; - - /* Parse the command line as defined by argtable[] */ - nerrors = arg_parse(argc,argv,argtable); - - /* special case: '--help' takes precedence over error reporting */ - if (help->count > 0) - { - printf("Usage: %s", progname); - arg_print_syntax(stdout,argtable,"\n"); - printf("This program demonstrates the use of the argtable2 library\n"); - printf("for parsing command line arguments.\n"); - arg_print_glossary(stdout,argtable," %-25s %s\n"); - exitcode=0; - goto exit; - } - - /* special case: '--version' takes precedence error reporting */ - if (version->count > 0) - { - printf("'%s' example program for the \"argtable\" command line argument parser.\n",progname); - printf("September 2003, Stewart Heitmann\n"); - exitcode=0; - goto exit; - } - - /* If the parser returned any errors then display them and exit */ - if (nerrors > 0) - { - /* Display the error details contained in the arg_end struct.*/ - arg_print_errors(stdout,end,progname); - printf("Try '%s --help' for more information.\n",progname); - exitcode=1; - goto exit; - } - - /* special case: uname with no command line options induces brief help */ - if (argc==1) - { - printf("Try '%s --help' for more information.\n",progname); - exitcode=0; - goto exit; - } - - /* normal case: take the command line options at face value */ - exitcode = mymain(list->count, recurse->count, repeat->ival[0], - defines->sval, defines->count, - outfile->filename[0], verbose->count, - infiles->filename, infiles->count); - - exit: - /* deallocate each non-null entry in argtable[] */ - arg_freetable(argtable,sizeof(argtable)/sizeof(argtable[0])); - - return exitcode; - } diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/examples/testargtable3.c b/parallel/parallel_src/extern/argtable3-3.2.2/examples/testargtable3.c deleted file mode 100644 index 91bef679..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/examples/testargtable3.c +++ /dev/null @@ -1,58 +0,0 @@ -#include "argtable3.h" - -/* global arg_xxx structs */ -struct arg_lit *a, *b, *c, *verb, *help, *version; -struct arg_int *scal; -struct arg_file *o, *file; -struct arg_end *end; - -int main(int argc, char *argv[]) -{ - /* the global arg_xxx structs are initialised within the argtable */ - void *argtable[] = { - help = arg_lit0(NULL, "help", "display this help and exit"), - version = arg_lit0(NULL, "version", "display version info and exit"), - a = arg_lit0("a", NULL,"the -a option"), - b = arg_lit0("b", NULL, "the -b option"), - c = arg_lit0("c", NULL, "the -c option"), - scal = arg_int0(NULL, "scalar", "", "foo value"), - verb = arg_lit0("v", "verbose", "verbose output"), - o = arg_file0("o", NULL, "myfile", "output file"), - file = arg_filen(NULL, NULL, "", 1, 100, "input files"), - end = arg_end(20), - }; - - int exitcode = 0; - char progname[] = "testargtable2.exe"; - - int nerrors; - nerrors = arg_parse(argc,argv,argtable); - - /* special case: '--help' takes precedence over error reporting */ - if (help->count > 0) - { - printf("Usage: %s", progname); - arg_print_syntax(stdout, argtable, "\n"); - printf("List information about the FILE(s) " - "(the current directory by default).\n\n"); - arg_print_glossary(stdout, argtable, " %-25s %s\n"); - exitcode = 0; - goto exit; - } - - /* If the parser returned any errors then display them and exit */ - if (nerrors > 0) - { - /* Display the error details contained in the arg_end struct.*/ - arg_print_errors(stdout, end, progname); - printf("Try '%s --help' for more information.\n", progname); - exitcode = 1; - goto exit; - } - -exit: - /* deallocate each non-null entry in argtable[] */ - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); - return exitcode; -} - diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/examples/uname.c b/parallel/parallel_src/extern/argtable3-3.2.2/examples/uname.c deleted file mode 100644 index e8e3f9a6..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/examples/uname.c +++ /dev/null @@ -1,137 +0,0 @@ -/******************************************************************************* - * Example source code for using the argtable3 library to implement: - * - * uname [-asnrvmpio] [--help] [--version] - * - * This file is part of the argtable3 library. - * - * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include "argtable3.h" - -/* Here we simulate the uname functionality */ -int mymain(int kname, int nname, int krel, int kver, int mach, int proc, int hard, int opsys) - { - if (kname) printf("Linux "); - if (nname) printf("localhost.localdomain "); - if (krel) printf("2.4.19-16 "); - if (kver) printf("#1 Fri Sep 20 18:15:05 CEST 2002 "); - if (mach) printf("i686 "); - if (proc) printf("Intel "); - if (hard) printf("unknown "); - if (opsys) printf("GNU/Linux "); - printf("\n"); - return 0; - } - - -int main(int argc, char **argv) - { - const char* progname = "uname"; - struct arg_lit *all = arg_lit0("a", "all", "print all information, in the following order:"); - struct arg_lit *kname = arg_lit0("s", "kernel-name", "print the kernel name"); - struct arg_lit *nname = arg_lit0("n", "nodename", "print the node name"); - struct arg_lit *krel = arg_lit0("r", "kernel-release", "print the kernel release"); - struct arg_lit *kver = arg_lit0("v", "kernel-version", "print the kernel version"); - struct arg_lit *mach = arg_lit0("m", "machine", "print the machine hardware name"); - struct arg_lit *proc = arg_lit0("p", "processor", "print the processor type"); - struct arg_lit *hard = arg_lit0("i", "hardware-platform","print the hardware platform"); - struct arg_lit *opsys = arg_lit0("o", "operating-system", "print the operating system"); - struct arg_lit *help = arg_lit0(NULL,"help", "print this help and exit"); - struct arg_lit *vers = arg_lit0(NULL,"version", "print version information and exit"); - struct arg_end *end = arg_end(20); - void* argtable[] = {all,kname,nname,krel,kver,mach,proc,hard,opsys,help,vers,end}; - int nerrors; - int exitcode=0; - - /* verify the argtable[] entries were allocated sucessfully */ - if (arg_nullcheck(argtable) != 0) - { - /* NULL entries were detected, some allocations must have failed */ - printf("%s: insufficient memory\n",progname); - exitcode=1; - goto exit; - } - - /* Parse the command line as defined by argtable[] */ - nerrors = arg_parse(argc,argv,argtable); - - /* special case: '--help' takes precedence over error reporting */ - if (help->count > 0) - { - printf("Usage: %s", progname); - arg_print_syntax(stdout,argtable,"\n"); - printf("Print certain system information. With no options, same as -s.\n\n"); - arg_print_glossary(stdout,argtable," %-25s %s\n"); - printf("\nReport bugs to .\n"); - exitcode=0; - goto exit; - } - - /* special case: '--version' takes precedence error reporting */ - if (vers->count > 0) - { - printf("'%s' example program for the \"argtable\" command line argument parser.\n",progname); - printf("September 2003, Stewart Heitmann\n"); - exitcode=0; - goto exit; - } - - /* If the parser returned any errors then display them and exit */ - if (nerrors > 0) - { - /* Display the error details contained in the arg_end struct.*/ - arg_print_errors(stdout,end,progname); - printf("Try '%s --help' for more information.\n",progname); - exitcode=1; - goto exit; - } - - /* special case: uname with no command line options is equivalent to "uname -s" */ - if (argc==1) - { - exitcode = mymain(0,1,0,0,0,0,0,0); - goto exit; - } - - /* special case: "uname -a" is equivalent to "uname -snrvmpi" */ - if (all->count>0) - { - exitcode = mymain(1,1,1,1,1,1,1,1); - goto exit; - } - - /* normal case: take the command line options at face value */ - exitcode = mymain(kname->count, nname->count, krel->count, kver->count, mach->count, proc->count, hard->count, opsys->count); - - exit: - /* deallocate each non-null entry in argtable[] */ - arg_freetable(argtable,sizeof(argtable)/sizeof(argtable[0])); - - return exitcode; - } diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/tests/CMakeLists.txt b/parallel/parallel_src/extern/argtable3-3.2.2/tests/CMakeLists.txt deleted file mode 100644 index 97c6b593..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/tests/CMakeLists.txt +++ /dev/null @@ -1,104 +0,0 @@ -################################################################################ -# This file is part of the argtable3 library. -# -# Copyright (C) 2016-2021 Tom G. Huang -# -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of STEWART HEITMANN nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, -# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -################################################################################ - -if(ARGTABLE3_ENABLE_ARG_REX_DEBUG) - add_definitions(-DARG_REX_DEBUG) -endif() - -if(NOT ARGTABLE3_REPLACE_GETOPT) - add_definitions(-DARG_REPLACE_GETOPT=0) -endif() - -if(ARGTABLE3_LONG_ONLY) - add_definitions(-DARG_LONG_ONLY) -endif() - -set(TEST_PUBLIC_SRC_FILES - testall.c - testarglit.c - testargstr.c - testargint.c - testargdate.c - testargdbl.c - testargfile.c - testargrex.c - testargdstr.c - testargcmd.c - CuTest.c -) - -set(TEST_SRC_FILES - ${TEST_PUBLIC_SRC_FILES} - testarghashtable.c -) - -if(UNIX) - set(ARGTABLE3_EXTRA_LIBS m) -endif() - -if(BUILD_SHARED_LIBS) - add_executable(test_shared ${TEST_PUBLIC_SRC_FILES}) - target_compile_definitions(test_shared PRIVATE -DARGTABLE3_TEST_PUBLIC_ONLY) - target_include_directories(test_shared PRIVATE ${PROJECT_SOURCE_DIR}/src) - target_link_libraries(test_shared argtable3 ${ARGTABLE3_EXTRA_LIBS}) - add_custom_command(TARGET test_shared POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "$" - "$" - ) - - add_test(NAME test_shared COMMAND "$") -else() - add_executable(test_static ${TEST_SRC_FILES}) - target_include_directories(test_static PRIVATE ${PROJECT_SOURCE_DIR}/src) - target_link_libraries(test_static argtable3 ${ARGTABLE3_EXTRA_LIBS}) - - add_test(NAME test_static COMMAND "$") -endif() - -add_executable(test_src ${TEST_SRC_FILES} ${ARGTABLE3_SRC_FILES}) -target_include_directories(test_src PRIVATE ${PROJECT_SOURCE_DIR}/src) -target_link_libraries(test_src ${ARGTABLE3_EXTRA_LIBS}) - -add_custom_command(OUTPUT ${ARGTABLE3_AMALGAMATION_SRC_FILE} - COMMAND "${PROJECT_SOURCE_DIR}/tools/build" dist - WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/tools" -) - -add_executable(test_amalgamation ${TEST_SRC_FILES} ${ARGTABLE3_AMALGAMATION_SRC_FILE}) -target_include_directories(test_amalgamation PRIVATE ${PROJECT_SOURCE_DIR}/src) -target_link_libraries(test_amalgamation ${ARGTABLE3_EXTRA_LIBS}) -add_custom_command(TARGET test_amalgamation PRE_BUILD - COMMAND "${PROJECT_SOURCE_DIR}/tools/build" dist - WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/tools" -) - -add_test(NAME test_src COMMAND "$") -add_test(NAME test_amalgamation COMMAND "$") diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/tests/CuTest.c b/parallel/parallel_src/extern/argtable3-3.2.2/tests/CuTest.c deleted file mode 100644 index 7aec35b5..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/tests/CuTest.c +++ /dev/null @@ -1,326 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "CuTest.h" - -/*-------------------------------------------------------------------------* - * CuStr - *-------------------------------------------------------------------------*/ - -char* CuStrAlloc(size_t size) { - char* newStr = (char*)malloc(sizeof(char) * (size)); - return newStr; -} - -char* CuStrCopy(const char* old) { - size_t len = strlen(old); - char* newStr = CuStrAlloc(len + 1); -#if (defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || (defined(__STDC_SECURE_LIB__) && defined(__STDC_WANT_SECURE_LIB__)) - strcpy_s(newStr, len + 1, old); -#else - strcpy(newStr, old); -#endif - - return newStr; -} - -/*-------------------------------------------------------------------------* - * CuString - *-------------------------------------------------------------------------*/ - -void CuStringInit(CuString* str) { - str->length = 0; - str->size = STRING_MAX; - str->buffer = (char*)malloc(sizeof(char) * str->size); - str->buffer[0] = '\0'; -} - -CuString* CuStringNew(void) { - CuString* str = (CuString*)malloc(sizeof(CuString)); - str->length = 0; - str->size = STRING_MAX; - str->buffer = (char*)malloc(sizeof(char) * str->size); - str->buffer[0] = '\0'; - return str; -} - -void CuStringDelete(CuString* str) { - if (!str) - return; - free(str->buffer); - free(str); -} - -void CuStringResize(CuString* str, size_t newSize) { - str->buffer = (char*)realloc(str->buffer, sizeof(char) * newSize); - str->size = newSize; -} - -void CuStringAppend(CuString* str, const char* text) { - size_t length; - - if (text == NULL) { - text = "NULL"; - } - - length = strlen(text); - if (str->length + length + 1 >= str->size) - CuStringResize(str, str->length + length + 1 + STRING_INC); - str->length += length; -#if (defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || (defined(__STDC_SECURE_LIB__) && defined(__STDC_WANT_SECURE_LIB__)) - strcat_s(str->buffer, str->size, text); -#else - strcat(str->buffer, text); -#endif -} - -void CuStringAppendChar(CuString* str, char ch) { - char text[2]; - text[0] = ch; - text[1] = '\0'; - CuStringAppend(str, text); -} - -void CuStringAppendFormat(CuString* str, const char* format, ...) { - va_list argp; - char buf[HUGE_STRING_LEN]; - va_start(argp, format); -#if (defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || (defined(__STDC_SECURE_LIB__) && defined(__STDC_WANT_SECURE_LIB__)) - vsprintf_s(buf, sizeof(buf), format, argp); -#else - vsprintf(buf, format, argp); -#endif - - va_end(argp); - CuStringAppend(str, buf); -} - -void CuStringInsert(CuString* str, const char* text, int pos) { - size_t length = strlen(text); - if ((size_t)pos > str->length) - pos = (int)str->length; - if (str->length + length + 1 >= str->size) - CuStringResize(str, str->length + length + 1 + STRING_INC); - memmove(str->buffer + pos + length, str->buffer + pos, (str->length - pos) + 1); - str->length += length; - memcpy(str->buffer + pos, text, length); -} - -/*-------------------------------------------------------------------------* - * CuTest - *-------------------------------------------------------------------------*/ - -void CuTestInit(CuTest* t, const char* name, TestFunction function) { - t->name = CuStrCopy(name); - t->failed = 0; - t->ran = 0; - t->message = NULL; - t->function = function; - t->jumpBuf = NULL; -} - -CuTest* CuTestNew(const char* name, TestFunction function) { - CuTest* tc = CU_ALLOC(CuTest); - CuTestInit(tc, name, function); - return tc; -} - -void CuTestDelete(CuTest* t) { - if (!t) - return; - free(t->name); - free(t); -} - -void CuTestRun(CuTest* tc) { - jmp_buf buf; - tc->jumpBuf = &buf; - if (setjmp(buf) == 0) { - tc->ran = 1; - (tc->function)(tc); - } - tc->jumpBuf = 0; -} - -static void CuFailInternal(CuTest* tc, const char* file, int line, CuString* string) { - char buf[HUGE_STRING_LEN]; - -#if (defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || (defined(__STDC_SECURE_LIB__) && defined(__STDC_WANT_SECURE_LIB__)) - sprintf_s(buf, sizeof(buf), "%s:%d: ", file, line); -#else - sprintf(buf, "%s:%d: ", file, line); -#endif - CuStringInsert(string, buf, 0); - - tc->failed = 1; - tc->message = string->buffer; - if (tc->jumpBuf != 0) - longjmp(*(tc->jumpBuf), 0); -} - -void CuFail_Line(CuTest* tc, const char* file, int line, const char* message2, const char* message) { - CuString string; - - CuStringInit(&string); - if (message2 != NULL) { - CuStringAppend(&string, message2); - CuStringAppend(&string, ": "); - } - CuStringAppend(&string, message); - CuFailInternal(tc, file, line, &string); -} - -void CuAssert_Line(CuTest* tc, const char* file, int line, const char* message, int condition) { - if (condition) - return; - CuFail_Line(tc, file, line, NULL, message); -} - -void CuAssertStrEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message, const char* expected, const char* actual) { - CuString string; - if ((expected == NULL && actual == NULL) || (expected != NULL && actual != NULL && strcmp(expected, actual) == 0)) { - return; - } - - CuStringInit(&string); - if (message != NULL) { - CuStringAppend(&string, message); - CuStringAppend(&string, ": "); - } - CuStringAppend(&string, "expected <"); - CuStringAppend(&string, expected); - CuStringAppend(&string, "> but was <"); - CuStringAppend(&string, actual); - CuStringAppend(&string, ">"); - CuFailInternal(tc, file, line, &string); -} - -void CuAssertIntEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message, int expected, int actual) { - char buf[STRING_MAX]; - if (expected == actual) - return; -#if (defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || (defined(__STDC_SECURE_LIB__) && defined(__STDC_WANT_SECURE_LIB__)) - sprintf_s(buf, sizeof(buf), "expected <%d> but was <%d>", expected, actual); -#else - sprintf(buf, "expected <%d> but was <%d>", expected, actual); -#endif - CuFail_Line(tc, file, line, message, buf); -} - -void CuAssertDblEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message, double expected, double actual, double delta) { - char buf[STRING_MAX]; - if (fabs(expected - actual) <= delta) - return; -#if (defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || (defined(__STDC_SECURE_LIB__) && defined(__STDC_WANT_SECURE_LIB__)) - sprintf_s(buf, sizeof(buf), "expected <%f> but was <%f>", expected, actual); -#else - sprintf(buf, "expected <%f> but was <%f>", expected, actual); -#endif - CuFail_Line(tc, file, line, message, buf); -} - -void CuAssertPtrEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message, void* expected, void* actual) { - char buf[STRING_MAX]; - if (expected == actual) - return; -#if (defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || (defined(__STDC_SECURE_LIB__) && defined(__STDC_WANT_SECURE_LIB__)) - sprintf_s(buf, sizeof(buf), "expected pointer <0x%p> but was <0x%p>", expected, actual); -#else - sprintf(buf, "expected pointer <0x%p> but was <0x%p>", expected, actual); -#endif - CuFail_Line(tc, file, line, message, buf); -} - -/*-------------------------------------------------------------------------* - * CuSuite - *-------------------------------------------------------------------------*/ - -void CuSuiteInit(CuSuite* testSuite) { - testSuite->count = 0; - testSuite->failCount = 0; - memset(testSuite->list, 0, sizeof(testSuite->list)); -} - -CuSuite* CuSuiteNew(void) { - CuSuite* testSuite = CU_ALLOC(CuSuite); - CuSuiteInit(testSuite); - return testSuite; -} - -void CuSuiteDelete(CuSuite* testSuite) { - unsigned int n; - for (n = 0; n < MAX_TEST_CASES; n++) { - if (testSuite->list[n]) { - CuTestDelete(testSuite->list[n]); - } - } - free(testSuite); -} - -void CuSuiteAdd(CuSuite* testSuite, CuTest* testCase) { - assert(testSuite->count < MAX_TEST_CASES); - testSuite->list[testSuite->count] = testCase; - testSuite->count++; -} - -void CuSuiteAddSuite(CuSuite* testSuite, CuSuite* testSuite2) { - int i; - for (i = 0; i < testSuite2->count; ++i) { - CuTest* testCase = testSuite2->list[i]; - CuSuiteAdd(testSuite, testCase); - } - free(testSuite2); -} - -void CuSuiteRun(CuSuite* testSuite) { - int i; - for (i = 0; i < testSuite->count; ++i) { - CuTest* testCase = testSuite->list[i]; - CuTestRun(testCase); - if (testCase->failed) { - testSuite->failCount += 1; - } - } -} - -void CuSuiteSummary(CuSuite* testSuite, CuString* summary) { - int i; - for (i = 0; i < testSuite->count; ++i) { - CuTest* testCase = testSuite->list[i]; - CuStringAppend(summary, testCase->failed ? "F" : "."); - } - CuStringAppend(summary, "\n\n"); -} - -void CuSuiteDetails(CuSuite* testSuite, CuString* details) { - int i; - int failCount = 0; - - if (testSuite->failCount == 0) { - int passCount = testSuite->count - testSuite->failCount; - const char* testWord = passCount == 1 ? "test" : "tests"; - CuStringAppendFormat(details, "OK (%d %s)\n", passCount, testWord); - } else { - if (testSuite->failCount == 1) - CuStringAppend(details, "There was 1 failure:\n"); - else - CuStringAppendFormat(details, "There were %d failures:\n", testSuite->failCount); - - for (i = 0; i < testSuite->count; ++i) { - CuTest* testCase = testSuite->list[i]; - if (testCase->failed) { - failCount++; - CuStringAppendFormat(details, "%d) %s: %s\n", failCount, testCase->name, testCase->message); - } - } - CuStringAppend(details, "\n!!!FAILURES!!!\n"); - - CuStringAppendFormat(details, "Runs: %d ", testSuite->count); - CuStringAppendFormat(details, "Passes: %d ", testSuite->count - testSuite->failCount); - CuStringAppendFormat(details, "Fails: %d\n", testSuite->failCount); - } -} diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/tests/CuTest.h b/parallel/parallel_src/extern/argtable3-3.2.2/tests/CuTest.h deleted file mode 100644 index 7c2cc6c3..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/tests/CuTest.h +++ /dev/null @@ -1,104 +0,0 @@ -#ifndef CU_TEST_H -#define CU_TEST_H - -#include -#include - -#define CUTEST_VERSION "CuTest 1.5" - -/* CuString */ - -char* CuStrAlloc(size_t size); -char* CuStrCopy(const char* old); - -#define CU_ALLOC(TYPE) ((TYPE*)malloc(sizeof(TYPE))) - -#define HUGE_STRING_LEN 8192 -#define STRING_MAX 256 -#define STRING_INC 256 - -typedef struct { - size_t length; - size_t size; - char* buffer; -} CuString; - -void CuStringInit(CuString* str); -CuString* CuStringNew(void); -void CuStringRead(CuString* str, const char* path); -void CuStringAppend(CuString* str, const char* text); -void CuStringAppendChar(CuString* str, char ch); -void CuStringAppendFormat(CuString* str, const char* format, ...); -void CuStringInsert(CuString* str, const char* text, int pos); -void CuStringResize(CuString* str, size_t newSize); -void CuStringDelete(CuString* str); - -/* CuTest */ - -typedef struct CuTest CuTest; - -typedef void (*TestFunction)(CuTest*); - -struct CuTest { - char* name; - TestFunction function; - int failed; - int ran; - const char* message; - jmp_buf* jumpBuf; -}; - -void CuTestInit(CuTest* t, const char* name, TestFunction function); -CuTest* CuTestNew(const char* name, TestFunction function); -void CuTestRun(CuTest* tc); -void CuTestDelete(CuTest* t); - -/* Internal versions of assert functions -- use the public versions */ -void CuFail_Line(CuTest* tc, const char* file, int line, const char* message2, const char* message); -void CuAssert_Line(CuTest* tc, const char* file, int line, const char* message, int condition); -void CuAssertStrEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message, const char* expected, const char* actual); -void CuAssertIntEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message, int expected, int actual); -void CuAssertDblEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message, double expected, double actual, double delta); -void CuAssertPtrEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message, void* expected, void* actual); - -/* public assert functions */ - -#define CuFail(tc, ms) CuFail_Line((tc), __FILE__, __LINE__, NULL, (ms)) -#define CuAssert(tc, ms, cond) CuAssert_Line((tc), __FILE__, __LINE__, (ms), (cond)) -#define CuAssertTrue(tc, cond) CuAssert_Line((tc), __FILE__, __LINE__, "assert failed", (cond)) - -#define CuAssertStrEquals(tc, ex, ac) CuAssertStrEquals_LineMsg((tc), __FILE__, __LINE__, NULL, (ex), (ac)) -#define CuAssertStrEquals_Msg(tc, ms, ex, ac) CuAssertStrEquals_LineMsg((tc), __FILE__, __LINE__, (ms), (ex), (ac)) -#define CuAssertIntEquals(tc, ex, ac) CuAssertIntEquals_LineMsg((tc), __FILE__, __LINE__, NULL, (ex), (ac)) -#define CuAssertIntEquals_Msg(tc, ms, ex, ac) CuAssertIntEquals_LineMsg((tc), __FILE__, __LINE__, (ms), (ex), (ac)) -#define CuAssertDblEquals(tc, ex, ac, dl) CuAssertDblEquals_LineMsg((tc), __FILE__, __LINE__, NULL, (ex), (ac), (dl)) -#define CuAssertDblEquals_Msg(tc, ms, ex, ac, dl) CuAssertDblEquals_LineMsg((tc), __FILE__, __LINE__, (ms), (ex), (ac), (dl)) -#define CuAssertPtrEquals(tc, ex, ac) CuAssertPtrEquals_LineMsg((tc), __FILE__, __LINE__, NULL, (ex), (ac)) -#define CuAssertPtrEquals_Msg(tc, ms, ex, ac) CuAssertPtrEquals_LineMsg((tc), __FILE__, __LINE__, (ms), (ex), (ac)) - -#define CuAssertPtrNotNull(tc, p) CuAssert_Line((tc), __FILE__, __LINE__, "null pointer unexpected", (p != NULL)) -#define CuAssertPtrNotNullMsg(tc, msg, p) CuAssert_Line((tc), __FILE__, __LINE__, (msg), (p != NULL)) - -/* CuSuite */ - -#define MAX_TEST_CASES 1024 - -#define SUITE_ADD_TEST(SUITE, TEST) CuSuiteAdd(SUITE, CuTestNew(#TEST, TEST)) - -typedef struct { - int count; - CuTest* list[MAX_TEST_CASES]; - int failCount; - -} CuSuite; - -void CuSuiteInit(CuSuite* testSuite); -CuSuite* CuSuiteNew(void); -void CuSuiteDelete(CuSuite* testSuite); -void CuSuiteAdd(CuSuite* testSuite, CuTest* testCase); -void CuSuiteAddSuite(CuSuite* testSuite, CuSuite* testSuite2); -void CuSuiteRun(CuSuite* testSuite); -void CuSuiteSummary(CuSuite* testSuite, CuString* summary); -void CuSuiteDetails(CuSuite* testSuite, CuString* details); - -#endif /* CU_TEST_H */ diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/tests/argtable3_private.h b/parallel/parallel_src/extern/argtable3-3.2.2/tests/argtable3_private.h deleted file mode 100644 index c174fa4b..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/tests/argtable3_private.h +++ /dev/null @@ -1,240 +0,0 @@ -/******************************************************************************* - * argtable3_private: Declares private types, constants, and interfaces - * - * This file is part of the argtable3 library. - * - * Copyright (C) 2013-2019 Tom G. Huang - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#ifndef ARG_UTILS_H -#define ARG_UTILS_H - -#include - -#define ARG_ENABLE_TRACE 0 -#define ARG_ENABLE_LOG 1 - -#ifdef __cplusplus -extern "C" { -#endif - -enum { ARG_ERR_MINCOUNT = 1, ARG_ERR_MAXCOUNT, ARG_ERR_BADINT, ARG_ERR_OVERFLOW, ARG_ERR_BADDOUBLE, ARG_ERR_BADDATE, ARG_ERR_REGNOMATCH }; - -typedef void(arg_panicfn)(const char* fmt, ...); - -#if defined(_MSC_VER) -#define ARG_TRACE(x) \ - __pragma(warning(push)) __pragma(warning(disable : 4127)) do { \ - if (ARG_ENABLE_TRACE) \ - dbg_printf x; \ - } \ - while (0) \ - __pragma(warning(pop)) - -#define ARG_LOG(x) \ - __pragma(warning(push)) __pragma(warning(disable : 4127)) do { \ - if (ARG_ENABLE_LOG) \ - dbg_printf x; \ - } \ - while (0) \ - __pragma(warning(pop)) -#else -#define ARG_TRACE(x) \ - do { \ - if (ARG_ENABLE_TRACE) \ - dbg_printf x; \ - } while (0) - -#define ARG_LOG(x) \ - do { \ - if (ARG_ENABLE_LOG) \ - dbg_printf x; \ - } while (0) -#endif - -/* - * Rename a few generic names to unique names. - * They can be a problem for the platforms like NuttX, where - * the namespace is flat for everything including apps and libraries. - */ -#define xmalloc argtable3_xmalloc -#define xcalloc argtable3_xcalloc -#define xrealloc argtable3_xrealloc -#define xfree argtable3_xfree - -extern void dbg_printf(const char* fmt, ...); -extern void arg_set_panic(arg_panicfn* proc); -extern void* xmalloc(size_t size); -extern void* xcalloc(size_t count, size_t size); -extern void* xrealloc(void* ptr, size_t size); -extern void xfree(void* ptr); - -struct arg_hashtable_entry { - void *k, *v; - unsigned int h; - struct arg_hashtable_entry* next; -}; - -typedef struct arg_hashtable { - unsigned int tablelength; - struct arg_hashtable_entry** table; - unsigned int entrycount; - unsigned int loadlimit; - unsigned int primeindex; - unsigned int (*hashfn)(const void* k); - int (*eqfn)(const void* k1, const void* k2); -} arg_hashtable_t; - -/** - * @brief Create a hash table. - * - * @param minsize minimum initial size of hash table - * @param hashfn function for hashing keys - * @param eqfn function for determining key equality - * @return newly created hash table or NULL on failure - */ -arg_hashtable_t* arg_hashtable_create(unsigned int minsize, unsigned int (*hashfn)(const void*), int (*eqfn)(const void*, const void*)); - -/** - * @brief This function will cause the table to expand if the insertion would take - * the ratio of entries to table size over the maximum load factor. - * - * This function does not check for repeated insertions with a duplicate key. - * The value returned when using a duplicate key is undefined -- when - * the hash table changes size, the order of retrieval of duplicate key - * entries is reversed. - * If in doubt, remove before insert. - * - * @param h the hash table to insert into - * @param k the key - hash table claims ownership and will free on removal - * @param v the value - does not claim ownership - * @return non-zero for successful insertion - */ -void arg_hashtable_insert(arg_hashtable_t* h, void* k, void* v); - -#define ARG_DEFINE_HASHTABLE_INSERT(fnname, keytype, valuetype) \ - int fnname(arg_hashtable_t* h, keytype* k, valuetype* v) { return arg_hashtable_insert(h, k, v); } - -/** - * @brief Search the specified key in the hash table. - * - * @param h the hash table to search - * @param k the key to search for - does not claim ownership - * @return the value associated with the key, or NULL if none found - */ -void* arg_hashtable_search(arg_hashtable_t* h, const void* k); - -#define ARG_DEFINE_HASHTABLE_SEARCH(fnname, keytype, valuetype) \ - valuetype* fnname(arg_hashtable_t* h, keytype* k) { return (valuetype*)(arg_hashtable_search(h, k)); } - -/** - * @brief Remove the specified key from the hash table. - * - * @param h the hash table to remove the item from - * @param k the key to search for - does not claim ownership - */ -void arg_hashtable_remove(arg_hashtable_t* h, const void* k); - -#define ARG_DEFINE_HASHTABLE_REMOVE(fnname, keytype, valuetype) \ - void fnname(arg_hashtable_t* h, keytype* k) { arg_hashtable_remove(h, k); } - -/** - * @brief Return the number of keys in the hash table. - * - * @param h the hash table - * @return the number of items stored in the hash table - */ -unsigned int arg_hashtable_count(arg_hashtable_t* h); - -/** - * @brief Change the value associated with the key. - * - * function to change the value associated with a key, where there already - * exists a value bound to the key in the hash table. - * Source due to Holger Schemel. - * - * @name hashtable_change - * @param h the hash table - * @param key - * @param value - */ -int arg_hashtable_change(arg_hashtable_t* h, void* k, void* v); - -/** - * @brief Free the hash table and the memory allocated for each key-value pair. - * - * @param h the hash table - * @param free_values whether to call 'free' on the remaining values - */ -void arg_hashtable_destroy(arg_hashtable_t* h, int free_values); - -typedef struct arg_hashtable_itr { - arg_hashtable_t* h; - struct arg_hashtable_entry* e; - struct arg_hashtable_entry* parent; - unsigned int index; -} arg_hashtable_itr_t; - -arg_hashtable_itr_t* arg_hashtable_itr_create(arg_hashtable_t* h); - -void arg_hashtable_itr_destroy(arg_hashtable_itr_t* itr); - -/** - * @brief Return the value of the (key,value) pair at the current position. - */ -extern void* arg_hashtable_itr_key(arg_hashtable_itr_t* i); - -/** - * @brief Return the value of the (key,value) pair at the current position. - */ -extern void* arg_hashtable_itr_value(arg_hashtable_itr_t* i); - -/** - * @brief Advance the iterator to the next element. Returns zero if advanced to end of table. - */ -int arg_hashtable_itr_advance(arg_hashtable_itr_t* itr); - -/** - * @brief Remove current element and advance the iterator to the next element. - */ -int arg_hashtable_itr_remove(arg_hashtable_itr_t* itr); - -/** - * @brief Search and overwrite the supplied iterator, to point to the entry matching the supplied key. - * - * @return Zero if not found. - */ -int arg_hashtable_itr_search(arg_hashtable_itr_t* itr, arg_hashtable_t* h, void* k); - -#define ARG_DEFINE_HASHTABLE_ITERATOR_SEARCH(fnname, keytype) \ - int fnname(arg_hashtable_itr_t* i, arg_hashtable_t* h, keytype* k) { return (arg_hashtable_iterator_search(i, h, k)); } - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/tests/testall.c b/parallel/parallel_src/extern/argtable3-3.2.2/tests/testall.c deleted file mode 100644 index b0f3d853..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/tests/testall.c +++ /dev/null @@ -1,80 +0,0 @@ -/******************************************************************************* - * This file is part of the argtable3 library. - * - * Copyright (C) 2013-2019 Tom G. Huang - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include - -#include "CuTest.h" - -CuSuite* get_arglit_testsuite(); -CuSuite* get_argstr_testsuite(); -CuSuite* get_argint_testsuite(); -CuSuite* get_argdate_testsuite(); -CuSuite* get_argdbl_testsuite(); -CuSuite* get_argfile_testsuite(); -CuSuite* get_argrex_testsuite(); -CuSuite* get_argdstr_testsuite(); -CuSuite* get_argcmd_testsuite(); - -#ifndef ARGTABLE3_TEST_PUBLIC_ONLY -CuSuite* get_arghashtable_testsuite(); -#endif - -int RunAllTests(void) { - CuString* output = CuStringNew(); - CuSuite* suite = CuSuiteNew(); - - CuSuiteAddSuite(suite, get_arglit_testsuite()); - CuSuiteAddSuite(suite, get_argstr_testsuite()); - CuSuiteAddSuite(suite, get_argint_testsuite()); - CuSuiteAddSuite(suite, get_argdate_testsuite()); - CuSuiteAddSuite(suite, get_argdbl_testsuite()); - CuSuiteAddSuite(suite, get_argfile_testsuite()); - CuSuiteAddSuite(suite, get_argrex_testsuite()); - CuSuiteAddSuite(suite, get_argdstr_testsuite()); - CuSuiteAddSuite(suite, get_argcmd_testsuite()); -#ifndef ARGTABLE3_TEST_PUBLIC_ONLY - CuSuiteAddSuite(suite, get_arghashtable_testsuite()); -#endif - - CuSuiteRun(suite); - CuSuiteSummary(suite, output); - CuSuiteDetails(suite, output); - printf("%s\n", output->buffer); - CuStringDelete(output); - - int failCount = suite->failCount; - CuSuiteDelete(suite); - - return failCount; -} - -int main(void) { - return RunAllTests(); -} diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/tests/testargcmd.c b/parallel/parallel_src/extern/argtable3-3.2.2/tests/testargcmd.c deleted file mode 100644 index 36dd275e..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/tests/testargcmd.c +++ /dev/null @@ -1,93 +0,0 @@ -/******************************************************************************* - * This file is part of the argtable3 library. - * - * Copyright (C) 2013-2019 Tom G. Huang - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include -#include - -#include - -#include "CuTest.h" -#include "argtable3.h" - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable : 4204) -#endif - -int cmd1_proc(int argc, char* argv[], arg_dstr_t res) { - if (argc == 0) { - arg_dstr_catf(res, "cmd1 fail"); - return 1; - } - - arg_dstr_catf(res, "%d %s", argc, argv[0]); - return 0; -} - -void test_argcmd_basic_001(CuTest* tc) { - arg_cmd_init(); - CuAssertIntEquals(tc, 0, arg_cmd_count()); - - arg_cmd_register("cmd1", cmd1_proc, "description of cmd1"); - CuAssertIntEquals(tc, 1, arg_cmd_count()); - - char* argv[] = { - "cmd1", - "-o", - "file1", - }; - int argc = 3; - CuAssertTrue(tc, strcmp(argv[0], "cmd1") == 0); - CuAssertTrue(tc, strcmp(argv[1], "-o") == 0); - CuAssertTrue(tc, strcmp(argv[2], "file1") == 0); - - arg_dstr_t res = arg_dstr_create(); - int err = arg_cmd_dispatch("cmd1", argc, argv, res); - CuAssertIntEquals(tc, 0, err); - CuAssertTrue(tc, strcmp(arg_dstr_cstr(res), "3 cmd1") == 0); - - arg_dstr_reset(res); - err = arg_cmd_dispatch("cmd1", 0, NULL, res); - CuAssertIntEquals(tc, 1, err); - CuAssertTrue(tc, strcmp(arg_dstr_cstr(res), "cmd1 fail") == 0); - - arg_dstr_destroy(res); - arg_cmd_uninit(); -} - -CuSuite* get_argcmd_testsuite() { - CuSuite* suite = CuSuiteNew(); - SUITE_ADD_TEST(suite, test_argcmd_basic_001); - return suite; -} - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/tests/testargdate.c b/parallel/parallel_src/extern/argtable3-3.2.2/tests/testargdate.c deleted file mode 100644 index e8f49c8e..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/tests/testargdate.c +++ /dev/null @@ -1,377 +0,0 @@ -/******************************************************************************* - * This file is part of the argtable3 library. - * - * Copyright (C) 2013-2019 Tom G. Huang - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include -#include - -#include "CuTest.h" -#include "argtable3.h" - -/* - printf("tm_sec = %d\n", c->tmval->tm_sec); - printf("tm_min = %d\n", c->tmval->tm_min); - printf("tm_hour = %d\n", c->tmval->tm_hour); - printf("tm_mday = %d\n", c->tmval->tm_mday); - printf("tm_mon = %d\n", c->tmval->tm_mon); - printf("tm_year = %d\n", c->tmval->tm_year); - printf("tm_wday = %d\n", c->tmval->tm_wday); - printf("tm_yday = %d\n", c->tmval->tm_yday); - printf("tm_isdst = %d\n", c->tmval->tm_isdst); - -*/ - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable : 4204) -#endif - -void test_argdate_basic_001(CuTest* tc) { - struct arg_date* a = arg_date1(NULL, NULL, "%H:%M", NULL, "time 23:59"); - struct arg_date* b = arg_date0("b", NULL, "%Y-%m-%d", NULL, "date YYYY-MM-DD"); - struct arg_date* c = arg_daten(NULL, "date", "%D", NULL, 1, 2, "MM/DD/YY"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, end}; - int nerrors; - - char* argv[] = {"program", "23:59", "--date", "12/31/04", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertIntEquals(tc, a->tmval->tm_sec, 0); - CuAssertIntEquals(tc, a->tmval->tm_min, 59); - CuAssertIntEquals(tc, a->tmval->tm_hour, 23); - CuAssertIntEquals(tc, a->tmval->tm_mday, 0); - CuAssertIntEquals(tc, a->tmval->tm_mon, 0); - CuAssertIntEquals(tc, a->tmval->tm_year, 0); - CuAssertIntEquals(tc, a->tmval->tm_wday, 0); - CuAssertIntEquals(tc, a->tmval->tm_yday, 0); - CuAssertIntEquals(tc, a->tmval->tm_isdst, 0); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 1); - CuAssertIntEquals(tc, c->tmval->tm_sec, 0); - CuAssertIntEquals(tc, c->tmval->tm_min, 0); - CuAssertIntEquals(tc, c->tmval->tm_hour, 0); - CuAssertIntEquals(tc, c->tmval->tm_mday, 31); - CuAssertIntEquals(tc, c->tmval->tm_mon, 11); - CuAssertIntEquals(tc, c->tmval->tm_year, 104); - CuAssertIntEquals(tc, c->tmval->tm_wday, 0); - CuAssertIntEquals(tc, c->tmval->tm_yday, 0); - CuAssertIntEquals(tc, c->tmval->tm_isdst, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argdate_basic_002(CuTest* tc) { - struct arg_date* a = arg_date1(NULL, NULL, "%H:%M", NULL, "time 23:59"); - struct arg_date* b = arg_date0("b", NULL, "%Y-%m-%d", NULL, "date YYYY-MM-DD"); - struct arg_date* c = arg_daten(NULL, "date", "%D", NULL, 1, 2, "MM/DD/YY"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, end}; - int nerrors; - - char* argv[] = {"program", "--date", "12/31/04", "20:15", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertIntEquals(tc, a->tmval->tm_sec, 0); - CuAssertIntEquals(tc, a->tmval->tm_min, 15); - CuAssertIntEquals(tc, a->tmval->tm_hour, 20); - CuAssertIntEquals(tc, a->tmval->tm_mday, 0); - CuAssertIntEquals(tc, a->tmval->tm_mon, 0); - CuAssertIntEquals(tc, a->tmval->tm_year, 0); - CuAssertIntEquals(tc, a->tmval->tm_wday, 0); - CuAssertIntEquals(tc, a->tmval->tm_yday, 0); - CuAssertIntEquals(tc, a->tmval->tm_isdst, 0); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 1); - CuAssertIntEquals(tc, c->tmval->tm_sec, 0); - CuAssertIntEquals(tc, c->tmval->tm_min, 0); - CuAssertIntEquals(tc, c->tmval->tm_hour, 0); - CuAssertIntEquals(tc, c->tmval->tm_mday, 31); - CuAssertIntEquals(tc, c->tmval->tm_mon, 11); - CuAssertIntEquals(tc, c->tmval->tm_year, 104); - CuAssertIntEquals(tc, c->tmval->tm_wday, 0); - CuAssertIntEquals(tc, c->tmval->tm_yday, 0); - CuAssertIntEquals(tc, c->tmval->tm_isdst, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argdate_basic_003(CuTest* tc) { - struct arg_date* a = arg_date1(NULL, NULL, "%H:%M", NULL, "time 23:59"); - struct arg_date* b = arg_date0("b", NULL, "%Y-%m-%d", NULL, "date YYYY-MM-DD"); - struct arg_date* c = arg_daten(NULL, "date", "%D", NULL, 1, 2, "MM/DD/YY"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, end}; - int nerrors; - - char* argv[] = {"program", "--date", "12/31/04", "20:15", "--date", "06/07/84", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertIntEquals(tc, a->tmval->tm_sec, 0); - CuAssertIntEquals(tc, a->tmval->tm_min, 15); - CuAssertIntEquals(tc, a->tmval->tm_hour, 20); - CuAssertIntEquals(tc, a->tmval->tm_mday, 0); - CuAssertIntEquals(tc, a->tmval->tm_mon, 0); - CuAssertIntEquals(tc, a->tmval->tm_year, 0); - CuAssertIntEquals(tc, a->tmval->tm_wday, 0); - CuAssertIntEquals(tc, a->tmval->tm_yday, 0); - CuAssertIntEquals(tc, a->tmval->tm_isdst, 0); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 2); - CuAssertIntEquals(tc, c->tmval->tm_sec, 0); - CuAssertIntEquals(tc, c->tmval->tm_min, 0); - CuAssertIntEquals(tc, c->tmval->tm_hour, 0); - CuAssertIntEquals(tc, c->tmval->tm_mday, 31); - CuAssertIntEquals(tc, c->tmval->tm_mon, 11); - CuAssertIntEquals(tc, c->tmval->tm_year, 104); - CuAssertIntEquals(tc, c->tmval->tm_wday, 0); - CuAssertIntEquals(tc, c->tmval->tm_yday, 0); - CuAssertIntEquals(tc, c->tmval->tm_isdst, 0); - CuAssertIntEquals(tc, (c->tmval + 1)->tm_sec, 0); - CuAssertIntEquals(tc, (c->tmval + 1)->tm_min, 0); - CuAssertIntEquals(tc, (c->tmval + 1)->tm_hour, 0); - CuAssertIntEquals(tc, (c->tmval + 1)->tm_mday, 7); - CuAssertIntEquals(tc, (c->tmval + 1)->tm_mon, 5); - CuAssertIntEquals(tc, (c->tmval + 1)->tm_year, 84); - CuAssertIntEquals(tc, (c->tmval + 1)->tm_wday, 0); - CuAssertIntEquals(tc, (c->tmval + 1)->tm_yday, 0); - CuAssertIntEquals(tc, (c->tmval + 1)->tm_isdst, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argdate_basic_004(CuTest* tc) { - struct arg_date* a = arg_date1(NULL, NULL, "%H:%M", NULL, "time 23:59"); - struct arg_date* b = arg_date0("b", NULL, "%Y-%m-%d", NULL, "date YYYY-MM-DD"); - struct arg_date* c = arg_daten(NULL, "date", "%D", NULL, 1, 2, "MM/DD/YY"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, end}; - int nerrors; - - char* argv[] = {"program", "--date", "12/31/04", "20:15", "-b", "1982-11-28", "--date", "06/07/84", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertIntEquals(tc, a->tmval->tm_sec, 0); - CuAssertIntEquals(tc, a->tmval->tm_min, 15); - CuAssertIntEquals(tc, a->tmval->tm_hour, 20); - CuAssertIntEquals(tc, a->tmval->tm_mday, 0); - CuAssertIntEquals(tc, a->tmval->tm_mon, 0); - CuAssertIntEquals(tc, a->tmval->tm_year, 0); - CuAssertIntEquals(tc, a->tmval->tm_wday, 0); - CuAssertIntEquals(tc, a->tmval->tm_yday, 0); - CuAssertIntEquals(tc, a->tmval->tm_isdst, 0); - CuAssertTrue(tc, b->count == 1); - CuAssertIntEquals(tc, b->tmval->tm_sec, 0); - CuAssertIntEquals(tc, b->tmval->tm_min, 0); - CuAssertIntEquals(tc, b->tmval->tm_hour, 0); - CuAssertIntEquals(tc, b->tmval->tm_mday, 28); - CuAssertIntEquals(tc, b->tmval->tm_mon, 10); - CuAssertIntEquals(tc, b->tmval->tm_year, 82); - CuAssertIntEquals(tc, b->tmval->tm_wday, 0); - CuAssertIntEquals(tc, b->tmval->tm_yday, 0); - CuAssertIntEquals(tc, b->tmval->tm_isdst, 0); - CuAssertTrue(tc, c->count == 2); - CuAssertIntEquals(tc, c->tmval->tm_sec, 0); - CuAssertIntEquals(tc, c->tmval->tm_min, 0); - CuAssertIntEquals(tc, c->tmval->tm_hour, 0); - CuAssertIntEquals(tc, c->tmval->tm_mday, 31); - CuAssertIntEquals(tc, c->tmval->tm_mon, 11); - CuAssertIntEquals(tc, c->tmval->tm_year, 104); - CuAssertIntEquals(tc, c->tmval->tm_wday, 0); - CuAssertIntEquals(tc, c->tmval->tm_yday, 0); - CuAssertIntEquals(tc, c->tmval->tm_isdst, 0); - CuAssertIntEquals(tc, (c->tmval + 1)->tm_sec, 0); - CuAssertIntEquals(tc, (c->tmval + 1)->tm_min, 0); - CuAssertIntEquals(tc, (c->tmval + 1)->tm_hour, 0); - CuAssertIntEquals(tc, (c->tmval + 1)->tm_mday, 7); - CuAssertIntEquals(tc, (c->tmval + 1)->tm_mon, 5); - CuAssertIntEquals(tc, (c->tmval + 1)->tm_year, 84); - CuAssertIntEquals(tc, (c->tmval + 1)->tm_wday, 0); - CuAssertIntEquals(tc, (c->tmval + 1)->tm_yday, 0); - CuAssertIntEquals(tc, (c->tmval + 1)->tm_isdst, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argdate_basic_005(CuTest* tc) { - struct arg_date* a = arg_date1(NULL, NULL, "%H:%M", NULL, "time 23:59"); - struct arg_date* b = arg_date0("b", NULL, "%Y-%m-%d", NULL, "date YYYY-MM-DD"); - struct arg_date* c = arg_daten(NULL, "date", "%D", NULL, 1, 2, "MM/DD/YY"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, end}; - int nerrors; - - char* argv[] = {"program", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 2); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argdate_basic_006(CuTest* tc) { - struct arg_date* a = arg_date1(NULL, NULL, "%H:%M", NULL, "time 23:59"); - struct arg_date* b = arg_date0("b", NULL, "%Y-%m-%d", NULL, "date YYYY-MM-DD"); - struct arg_date* c = arg_daten(NULL, "date", "%D", NULL, 1, 2, "MM/DD/YY"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, end}; - int nerrors; - - char* argv[] = {"program", "25:59", "--date", "12/31/04", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argdate_basic_007(CuTest* tc) { - struct arg_date* a = arg_date1(NULL, NULL, "%H:%M", NULL, "time 23:59"); - struct arg_date* b = arg_date0("b", NULL, "%Y-%m-%d", NULL, "date YYYY-MM-DD"); - struct arg_date* c = arg_daten(NULL, "date", "%D", NULL, 1, 2, "MM/DD/YY"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, end}; - int nerrors; - - char* argv[] = {"program", "23:59", "--date", "12/32/04", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argdate_basic_008(CuTest* tc) { - struct arg_date* a = arg_date1(NULL, NULL, "%H:%M", NULL, "time 23:59"); - struct arg_date* b = arg_date0("b", NULL, "%Y-%m-%d", NULL, "date YYYY-MM-DD"); - struct arg_date* c = arg_daten(NULL, "date", "%D", NULL, 1, 2, "MM/DD/YY"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, end}; - int nerrors; - - char* argv[] = {"program", "23:59", "--date", "12/31/04", "22:58", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argdate_basic_009(CuTest* tc) { - struct arg_date* a = arg_date1(NULL, NULL, "%H:%M", NULL, "time 23:59"); - struct arg_date* b = arg_date0("b", NULL, "%Y-%m-%d", NULL, "date YYYY-MM-DD"); - struct arg_date* c = arg_daten(NULL, "date", "%D", NULL, 1, 2, "MM/DD/YY"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, end}; - int nerrors; - - char* argv[] = {"program", "--date", "12/31/04", "20:15", "--date", "26/07/84", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argdate_basic_010(CuTest* tc) { - struct arg_date* a = arg_date1(NULL, NULL, "%H:%M", NULL, "time 23:59"); - struct arg_date* b = arg_date0("b", NULL, "%Y-%m-%d", NULL, "date YYYY-MM-DD"); - struct arg_date* c = arg_daten(NULL, "date", "%D", NULL, 1, 2, "MM/DD/YY"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, end}; - int nerrors; - - char* argv[] = {"program", "-b", "1982-11-28", "-b", "1976-11-11", "--date", "12/07/84", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -CuSuite* get_argdate_testsuite() { - CuSuite* suite = CuSuiteNew(); - SUITE_ADD_TEST(suite, test_argdate_basic_001); - SUITE_ADD_TEST(suite, test_argdate_basic_002); - SUITE_ADD_TEST(suite, test_argdate_basic_003); - SUITE_ADD_TEST(suite, test_argdate_basic_004); - SUITE_ADD_TEST(suite, test_argdate_basic_005); - SUITE_ADD_TEST(suite, test_argdate_basic_006); - SUITE_ADD_TEST(suite, test_argdate_basic_007); - SUITE_ADD_TEST(suite, test_argdate_basic_008); - SUITE_ADD_TEST(suite, test_argdate_basic_009); - SUITE_ADD_TEST(suite, test_argdate_basic_010); - return suite; -} - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/tests/testargdbl.c b/parallel/parallel_src/extern/argtable3-3.2.2/tests/testargdbl.c deleted file mode 100644 index 4cb0f7dd..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/tests/testargdbl.c +++ /dev/null @@ -1,451 +0,0 @@ -/******************************************************************************* - * This file is part of the argtable3 library. - * - * Copyright (C) 2013-2019 Tom G. Huang - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include -#include - -#include "CuTest.h" -#include "argtable3.h" - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable : 4204) -#endif - -void test_argdbl_basic_001(CuTest* tc) { - struct arg_dbl* a = arg_dbl1(NULL, NULL, "a", "a is "); - struct arg_dbl* b = arg_dbl0(NULL, NULL, "b", "b is "); - struct arg_dbl* c = arg_dbl0(NULL, NULL, "c", "c is "); - struct arg_dbl* d = arg_dbln("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_dbl* e = arg_dbl0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, end}; - int nerrors; - - char* argv[] = {"program", "0", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 0); - - CuAssertTrue(tc, a->count == 1); - CuAssertDblEquals(tc, a->dval[0], 0, DBL_EPSILON); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 0); - CuAssertTrue(tc, d->count == 0); - CuAssertTrue(tc, e->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argdbl_basic_002(CuTest* tc) { - struct arg_dbl* a = arg_dbl1(NULL, NULL, "a", "a is "); - struct arg_dbl* b = arg_dbl0(NULL, NULL, "b", "b is "); - struct arg_dbl* c = arg_dbl0(NULL, NULL, "c", "c is "); - struct arg_dbl* d = arg_dbln("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_dbl* e = arg_dbl0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, end}; - int nerrors; - - char* argv[] = {"program", "1.234", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 0); - - CuAssertTrue(tc, a->count == 1); - CuAssertDblEquals(tc, a->dval[0], 1.234, DBL_EPSILON); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 0); - CuAssertTrue(tc, d->count == 0); - CuAssertTrue(tc, e->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argdbl_basic_003(CuTest* tc) { - struct arg_dbl* a = arg_dbl1(NULL, NULL, "a", "a is "); - struct arg_dbl* b = arg_dbl0(NULL, NULL, "b", "b is "); - struct arg_dbl* c = arg_dbl0(NULL, NULL, "c", "c is "); - struct arg_dbl* d = arg_dbln("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_dbl* e = arg_dbl0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, end}; - int nerrors; - - char* argv[] = {"program", "1.8", "2.3", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 0); - - CuAssertTrue(tc, a->count == 1); - CuAssertDblEquals(tc, a->dval[0], 1.8, DBL_EPSILON); - CuAssertTrue(tc, b->count == 1); - CuAssertDblEquals(tc, b->dval[0], 2.3, DBL_EPSILON); - CuAssertTrue(tc, c->count == 0); - CuAssertTrue(tc, d->count == 0); - CuAssertTrue(tc, e->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argdbl_basic_004(CuTest* tc) { - struct arg_dbl* a = arg_dbl1(NULL, NULL, "a", "a is "); - struct arg_dbl* b = arg_dbl0(NULL, NULL, "b", "b is "); - struct arg_dbl* c = arg_dbl0(NULL, NULL, "c", "c is "); - struct arg_dbl* d = arg_dbln("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_dbl* e = arg_dbl0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, end}; - int nerrors; - - char* argv[] = {"program", "5", "7", "9", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 0); - - CuAssertTrue(tc, a->count == 1); - CuAssertDblEquals(tc, a->dval[0], 5, DBL_EPSILON); - CuAssertTrue(tc, b->count == 1); - CuAssertDblEquals(tc, b->dval[0], 7, DBL_EPSILON); - CuAssertTrue(tc, c->count == 1); - CuAssertDblEquals(tc, c->dval[0], 9, DBL_EPSILON); - CuAssertTrue(tc, d->count == 0); - CuAssertTrue(tc, e->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argdbl_basic_005(CuTest* tc) { - struct arg_dbl* a = arg_dbl1(NULL, NULL, "a", "a is "); - struct arg_dbl* b = arg_dbl0(NULL, NULL, "b", "b is "); - struct arg_dbl* c = arg_dbl0(NULL, NULL, "c", "c is "); - struct arg_dbl* d = arg_dbln("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_dbl* e = arg_dbl0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, end}; - int nerrors; - - char* argv[] = {"program", "1.9998", "-d", "13e-1", "-D", "17e-1", "--delta", "36e-1", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 0); - - CuAssertTrue(tc, a->count == 1); - CuAssertDblEquals(tc, a->dval[0], 1.9998, DBL_EPSILON); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 0); - CuAssertTrue(tc, d->count == 3); - CuAssertDblEquals(tc, d->dval[0], 13e-1, DBL_EPSILON); - CuAssertDblEquals(tc, d->dval[1], 17e-1, DBL_EPSILON); - CuAssertDblEquals(tc, d->dval[2], 36e-1, DBL_EPSILON); - CuAssertTrue(tc, e->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argdbl_basic_006(CuTest* tc) { - struct arg_dbl* a = arg_dbl1(NULL, NULL, "a", "a is "); - struct arg_dbl* b = arg_dbl0(NULL, NULL, "b", "b is "); - struct arg_dbl* c = arg_dbl0(NULL, NULL, "c", "c is "); - struct arg_dbl* d = arg_dbln("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_dbl* e = arg_dbl0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, end}; - int nerrors; - - char* argv[] = {"program", "1.2", "2.3", "4.5", "--eps", "8.3456789", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 0); - - CuAssertTrue(tc, a->count == 1); - CuAssertDblEquals(tc, a->dval[0], 1.2, DBL_EPSILON); - CuAssertTrue(tc, b->count == 1); - CuAssertDblEquals(tc, b->dval[0], 2.3, DBL_EPSILON); - CuAssertTrue(tc, c->count == 1); - CuAssertDblEquals(tc, c->dval[0], 4.5, DBL_EPSILON); - CuAssertTrue(tc, d->count == 0); - CuAssertTrue(tc, e->count == 1); - CuAssertDblEquals(tc, e->dval[0], 8.3456789, DBL_EPSILON); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argdbl_basic_007(CuTest* tc) { - struct arg_dbl* a = arg_dbl1(NULL, NULL, "a", "a is "); - struct arg_dbl* b = arg_dbl0(NULL, NULL, "b", "b is "); - struct arg_dbl* c = arg_dbl0(NULL, NULL, "c", "c is "); - struct arg_dbl* d = arg_dbln("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_dbl* e = arg_dbl0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, end}; - int nerrors; - - char* argv[] = {"program", "1.2", "2.3", "4.5", "--eqn", "8.3456789", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 0); - - CuAssertTrue(tc, a->count == 1); - CuAssertDblEquals(tc, a->dval[0], 1.2, DBL_EPSILON); - CuAssertTrue(tc, b->count == 1); - CuAssertDblEquals(tc, b->dval[0], 2.3, DBL_EPSILON); - CuAssertTrue(tc, c->count == 1); - CuAssertDblEquals(tc, c->dval[0], 4.5, DBL_EPSILON); - CuAssertTrue(tc, d->count == 0); - CuAssertTrue(tc, e->count == 1); - CuAssertDblEquals(tc, e->dval[0], 8.3456789, DBL_EPSILON); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argdbl_basic_008(CuTest* tc) { - struct arg_dbl* a = arg_dbl1(NULL, NULL, "a", "a is "); - struct arg_dbl* b = arg_dbl0(NULL, NULL, "b", "b is "); - struct arg_dbl* c = arg_dbl0(NULL, NULL, "c", "c is "); - struct arg_dbl* d = arg_dbln("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_dbl* e = arg_dbl0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, end}; - int nerrors; - - char* argv[] = {"program", "1.2", "2.3", "4.5", "--eqn", "8.345", "-D", "0.234", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 0); - - CuAssertTrue(tc, a->count == 1); - CuAssertDblEquals(tc, a->dval[0], 1.2, DBL_EPSILON); - CuAssertTrue(tc, b->count == 1); - CuAssertDblEquals(tc, b->dval[0], 2.3, DBL_EPSILON); - CuAssertTrue(tc, c->count == 1); - CuAssertDblEquals(tc, c->dval[0], 4.5, DBL_EPSILON); - CuAssertTrue(tc, d->count == 1); - CuAssertDblEquals(tc, d->dval[0], 0.234, DBL_EPSILON); - CuAssertTrue(tc, e->count == 1); - CuAssertDblEquals(tc, e->dval[0], 8.345, DBL_EPSILON); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argdbl_basic_009(CuTest* tc) { - struct arg_dbl* a = arg_dbl1(NULL, NULL, "a", "a is "); - struct arg_dbl* b = arg_dbl0(NULL, NULL, "b", "b is "); - struct arg_dbl* c = arg_dbl0(NULL, NULL, "c", "c is "); - struct arg_dbl* d = arg_dbln("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_dbl* e = arg_dbl0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, end}; - int nerrors; - - char* argv[] = {"program", "1", "2", "3", "4", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argdbl_basic_010(CuTest* tc) { - struct arg_dbl* a = arg_dbl1(NULL, NULL, "a", "a is "); - struct arg_dbl* b = arg_dbl0(NULL, NULL, "b", "b is "); - struct arg_dbl* c = arg_dbl0(NULL, NULL, "c", "c is "); - struct arg_dbl* d = arg_dbln("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_dbl* e = arg_dbl0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, end}; - int nerrors; - - char* argv[] = {"program", "1", "2", "3", "4", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argdbl_basic_011(CuTest* tc) { - struct arg_dbl* a = arg_dbl1(NULL, NULL, "a", "a is "); - struct arg_dbl* b = arg_dbl0(NULL, NULL, "b", "b is "); - struct arg_dbl* c = arg_dbl0(NULL, NULL, "c", "c is "); - struct arg_dbl* d = arg_dbln("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_dbl* e = arg_dbl0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, end}; - int nerrors; - - char* argv[] = {"program", "1", "2", "3", "-d1", "-d2", "-d3", "-d4", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argdbl_basic_012(CuTest* tc) { - struct arg_dbl* a = arg_dbl1(NULL, NULL, "a", "a is "); - struct arg_dbl* b = arg_dbl0(NULL, NULL, "b", "b is "); - struct arg_dbl* c = arg_dbl0(NULL, NULL, "c", "c is "); - struct arg_dbl* d = arg_dbln("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_dbl* e = arg_dbl0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, end}; - int nerrors; - - char* argv[] = {"program", "1", "2", "3", "--eps", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argdbl_basic_013(CuTest* tc) { - struct arg_dbl* a = arg_dbl1(NULL, NULL, "a", "a is "); - struct arg_dbl* b = arg_dbl0(NULL, NULL, "b", "b is "); - struct arg_dbl* c = arg_dbl0(NULL, NULL, "c", "c is "); - struct arg_dbl* d = arg_dbln("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_dbl* e = arg_dbl0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, end}; - int nerrors; - - char* argv[] = {"program", "1", "2", "3", "--eps", "3", "--eqn", "6", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argdbl_basic_014(CuTest* tc) { - struct arg_dbl* a = arg_dbl1(NULL, NULL, "a", "a is "); - struct arg_dbl* b = arg_dbl0(NULL, NULL, "b", "b is "); - struct arg_dbl* c = arg_dbl0(NULL, NULL, "c", "c is "); - struct arg_dbl* d = arg_dbln("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_dbl* e = arg_dbl0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, end}; - int nerrors; - - char* argv[] = {"program", "hello", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argdbl_basic_015(CuTest* tc) { - struct arg_dbl* a = arg_dbl1(NULL, NULL, "a", "a is "); - struct arg_dbl* b = arg_dbl0(NULL, NULL, "b", "b is "); - struct arg_dbl* c = arg_dbl0(NULL, NULL, "c", "c is "); - struct arg_dbl* d = arg_dbln("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_dbl* e = arg_dbl0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, end}; - int nerrors; - - char* argv[] = {"program", "4", "hello", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -CuSuite* get_argdbl_testsuite() { - CuSuite* suite = CuSuiteNew(); - SUITE_ADD_TEST(suite, test_argdbl_basic_001); - SUITE_ADD_TEST(suite, test_argdbl_basic_002); - SUITE_ADD_TEST(suite, test_argdbl_basic_003); - SUITE_ADD_TEST(suite, test_argdbl_basic_004); - SUITE_ADD_TEST(suite, test_argdbl_basic_005); - SUITE_ADD_TEST(suite, test_argdbl_basic_006); - SUITE_ADD_TEST(suite, test_argdbl_basic_007); - SUITE_ADD_TEST(suite, test_argdbl_basic_008); - SUITE_ADD_TEST(suite, test_argdbl_basic_009); - SUITE_ADD_TEST(suite, test_argdbl_basic_010); - SUITE_ADD_TEST(suite, test_argdbl_basic_011); - SUITE_ADD_TEST(suite, test_argdbl_basic_012); - SUITE_ADD_TEST(suite, test_argdbl_basic_013); - SUITE_ADD_TEST(suite, test_argdbl_basic_014); - SUITE_ADD_TEST(suite, test_argdbl_basic_015); - return suite; -} - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/tests/testargdstr.c b/parallel/parallel_src/extern/argtable3-3.2.2/tests/testargdstr.c deleted file mode 100644 index 54c79870..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/tests/testargdstr.c +++ /dev/null @@ -1,123 +0,0 @@ -/******************************************************************************* - * This file is part of the argtable3 library. - * - * Copyright (C) 2013-2019 Tom G. Huang - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include -#include - -#include - -#include "CuTest.h" -#include "argtable3.h" - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable : 4204) -#endif - -void test_argdstr_basic_001(CuTest* tc) { - arg_dstr_t ds = arg_dstr_create(); - - arg_dstr_set(ds, "hello ", ARG_DSTR_VOLATILE); - CuAssertTrue(tc, strcmp(arg_dstr_cstr(ds), "hello ") == 0); - - arg_dstr_cat(ds, "world"); - CuAssertTrue(tc, strcmp(arg_dstr_cstr(ds), "hello world") == 0); - - arg_dstr_destroy(ds); -} - -void test_argdstr_basic_002(CuTest* tc) { - arg_dstr_t ds = arg_dstr_create(); - - arg_dstr_set(ds, "hello world", ARG_DSTR_VOLATILE); - CuAssertTrue(tc, strcmp(arg_dstr_cstr(ds), "hello world") == 0); - - arg_dstr_reset(ds); - CuAssertTrue(tc, strcmp(arg_dstr_cstr(ds), "") == 0); - - arg_dstr_set(ds, "good", ARG_DSTR_VOLATILE); - CuAssertTrue(tc, strcmp(arg_dstr_cstr(ds), "good") == 0); - - arg_dstr_destroy(ds); -} - -void test_argdstr_basic_003(CuTest* tc) { - arg_dstr_t ds = arg_dstr_create(); - arg_dstr_cat(ds, "hello world"); - CuAssertTrue(tc, strcmp(arg_dstr_cstr(ds), "hello world") == 0); - - arg_dstr_destroy(ds); -} - -void test_argdstr_basic_004(CuTest* tc) { - arg_dstr_t ds = arg_dstr_create(); - arg_dstr_catf(ds, "%s %d", "hello world", 1); - CuAssertTrue(tc, strcmp(arg_dstr_cstr(ds), "hello world 1") == 0); - - arg_dstr_destroy(ds); -} - -void test_argdstr_basic_005(CuTest* tc) { - arg_dstr_t ds = arg_dstr_create(); - arg_dstr_catf(ds, "%d.", 1); - arg_dstr_catf(ds, "%d.", 2); - arg_dstr_catf(ds, "%d.", 3); - arg_dstr_cat(ds, "456"); - CuAssertTrue(tc, strcmp(arg_dstr_cstr(ds), "1.2.3.456") == 0); - - arg_dstr_destroy(ds); -} - -void test_argdstr_basic_006(CuTest* tc) { - int i; - - arg_dstr_t ds = arg_dstr_create(); - for (i = 0; i < 100000; i++) { - arg_dstr_catf(ds, "%s", "1234567890"); - } - CuAssertTrue(tc, strlen(arg_dstr_cstr(ds)) == 1000000); - - arg_dstr_destroy(ds); -} - -CuSuite* get_argdstr_testsuite() { - CuSuite* suite = CuSuiteNew(); - SUITE_ADD_TEST(suite, test_argdstr_basic_001); - SUITE_ADD_TEST(suite, test_argdstr_basic_002); - SUITE_ADD_TEST(suite, test_argdstr_basic_003); - SUITE_ADD_TEST(suite, test_argdstr_basic_004); - SUITE_ADD_TEST(suite, test_argdstr_basic_005); - SUITE_ADD_TEST(suite, test_argdstr_basic_006); - return suite; -} - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/tests/testargfile.c b/parallel/parallel_src/extern/argtable3-3.2.2/tests/testargfile.c deleted file mode 100644 index ad457e4f..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/tests/testargfile.c +++ /dev/null @@ -1,809 +0,0 @@ -/******************************************************************************* - * This file is part of the argtable3 library. - * - * Copyright (C) 2013-2019 Tom G. Huang - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include - -#include "CuTest.h" -#include "argtable3.h" - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable : 4204) -#endif - -void test_argfile_basic_001(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "foo.bar", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "foo.bar"); - CuAssertStrEquals(tc, a->basename[0], "foo.bar"); - CuAssertStrEquals(tc, a->extension[0], ".bar"); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_002(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "/foo.bar", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "/foo.bar"); - CuAssertStrEquals(tc, a->basename[0], "foo.bar"); - CuAssertStrEquals(tc, a->extension[0], ".bar"); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_003(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "./foo.bar", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "./foo.bar"); - CuAssertStrEquals(tc, a->basename[0], "foo.bar"); - CuAssertStrEquals(tc, a->extension[0], ".bar"); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_004(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "././foo.bar", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "././foo.bar"); - CuAssertStrEquals(tc, a->basename[0], "foo.bar"); - CuAssertStrEquals(tc, a->extension[0], ".bar"); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_005(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "./././foo.bar", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "./././foo.bar"); - CuAssertStrEquals(tc, a->basename[0], "foo.bar"); - CuAssertStrEquals(tc, a->extension[0], ".bar"); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_006(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "../foo.bar", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "../foo.bar"); - CuAssertStrEquals(tc, a->basename[0], "foo.bar"); - CuAssertStrEquals(tc, a->extension[0], ".bar"); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_007(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "../../foo.bar", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "../../foo.bar"); - CuAssertStrEquals(tc, a->basename[0], "foo.bar"); - CuAssertStrEquals(tc, a->extension[0], ".bar"); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_008(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "foo", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "foo"); - CuAssertStrEquals(tc, a->basename[0], "foo"); - CuAssertStrEquals(tc, a->extension[0], ""); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_009(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "/foo", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "/foo"); - CuAssertStrEquals(tc, a->basename[0], "foo"); - CuAssertStrEquals(tc, a->extension[0], ""); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_010(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "./foo", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "./foo"); - CuAssertStrEquals(tc, a->basename[0], "foo"); - CuAssertStrEquals(tc, a->extension[0], ""); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_011(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "././foo", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "././foo"); - CuAssertStrEquals(tc, a->basename[0], "foo"); - CuAssertStrEquals(tc, a->extension[0], ""); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_012(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "./././foo", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "./././foo"); - CuAssertStrEquals(tc, a->basename[0], "foo"); - CuAssertStrEquals(tc, a->extension[0], ""); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_013(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "../foo", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "../foo"); - CuAssertStrEquals(tc, a->basename[0], "foo"); - CuAssertStrEquals(tc, a->extension[0], ""); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_014(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "../../foo", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "../../foo"); - CuAssertStrEquals(tc, a->basename[0], "foo"); - CuAssertStrEquals(tc, a->extension[0], ""); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_015(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", ".foo", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], ".foo"); - CuAssertStrEquals(tc, a->basename[0], ".foo"); - CuAssertStrEquals(tc, a->extension[0], ""); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_016(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "/.foo", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "/.foo"); - CuAssertStrEquals(tc, a->basename[0], ".foo"); - CuAssertStrEquals(tc, a->extension[0], ""); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_017(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "./.foo", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "./.foo"); - CuAssertStrEquals(tc, a->basename[0], ".foo"); - CuAssertStrEquals(tc, a->extension[0], ""); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_018(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "../.foo", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "../.foo"); - CuAssertStrEquals(tc, a->basename[0], ".foo"); - CuAssertStrEquals(tc, a->extension[0], ""); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_019(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "foo.", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "foo."); - CuAssertStrEquals(tc, a->basename[0], "foo."); - CuAssertStrEquals(tc, a->extension[0], ""); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_020(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "/foo.", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "/foo."); - CuAssertStrEquals(tc, a->basename[0], "foo."); - CuAssertStrEquals(tc, a->extension[0], ""); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_021(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "./foo.", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "./foo."); - CuAssertStrEquals(tc, a->basename[0], "foo."); - CuAssertStrEquals(tc, a->extension[0], ""); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_022(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "../foo.", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "../foo."); - CuAssertStrEquals(tc, a->basename[0], "foo."); - CuAssertStrEquals(tc, a->extension[0], ""); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_023(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "/.foo.", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "/.foo."); - CuAssertStrEquals(tc, a->basename[0], ".foo."); - CuAssertStrEquals(tc, a->extension[0], ""); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_024(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "/.foo.c", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "/.foo.c"); - CuAssertStrEquals(tc, a->basename[0], ".foo.c"); - CuAssertStrEquals(tc, a->extension[0], ".c"); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_025(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "/.foo..b.c", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "/.foo..b.c"); - CuAssertStrEquals(tc, a->basename[0], ".foo..b.c"); - CuAssertStrEquals(tc, a->extension[0], ".c"); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_026(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "/", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "/"); - CuAssertStrEquals(tc, a->basename[0], ""); - CuAssertStrEquals(tc, a->extension[0], ""); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_027(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", ".", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "."); - CuAssertStrEquals(tc, a->basename[0], ""); - CuAssertStrEquals(tc, a->extension[0], ""); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_028(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "..", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], ".."); - CuAssertStrEquals(tc, a->basename[0], ""); - CuAssertStrEquals(tc, a->extension[0], ""); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_029(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "/.", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "/."); - CuAssertStrEquals(tc, a->basename[0], ""); - CuAssertStrEquals(tc, a->extension[0], ""); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_030(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "/..", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "/.."); - CuAssertStrEquals(tc, a->basename[0], ""); - CuAssertStrEquals(tc, a->extension[0], ""); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_031(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "./", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "./"); - CuAssertStrEquals(tc, a->basename[0], ""); - CuAssertStrEquals(tc, a->extension[0], ""); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_032(CuTest* tc) { - struct arg_file* a = arg_file1(NULL, NULL, "", "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "../", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "../"); - CuAssertStrEquals(tc, a->basename[0], ""); - CuAssertStrEquals(tc, a->extension[0], ""); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -#ifdef WIN32 -void test_argfile_basic_033(CuTest* tc) { - struct arg_file* a = arg_filen(NULL, NULL, "", 0, 3, "filename to test"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, end}; - int nerrors; - - char* argv[] = {"program", "C:\\test folder\\", "C:\\test folder2", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 2); - CuAssertStrEquals(tc, a->filename[0], "C:\\test folder\\"); - CuAssertStrEquals(tc, a->basename[0], ""); - CuAssertStrEquals(tc, a->extension[0], ""); - CuAssertStrEquals(tc, a->filename[1], "C:\\test folder2"); - CuAssertStrEquals(tc, a->basename[1], "test folder2"); - CuAssertStrEquals(tc, a->extension[1], ""); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argfile_basic_034(CuTest* tc) { - struct arg_file* a = arg_filen(NULL, NULL, "", 1, 1, "path a"); - struct arg_file* b = arg_filen(NULL, NULL, "", 1, 1, "path b"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, end}; - int nerrors; - - char* argv[] = {"program", "C:\\test folder\\", "C:\\test folder2", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->filename[0], "C:\\test folder\\"); - CuAssertStrEquals(tc, a->basename[0], ""); - CuAssertStrEquals(tc, a->extension[0], ""); - CuAssertTrue(tc, b->count == 1); - CuAssertStrEquals(tc, b->filename[0], "C:\\test folder2"); - CuAssertStrEquals(tc, b->basename[0], "test folder2"); - CuAssertStrEquals(tc, b->extension[0], ""); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} -#endif /* #ifdef WIN32 */ - -CuSuite* get_argfile_testsuite() { - CuSuite* suite = CuSuiteNew(); - SUITE_ADD_TEST(suite, test_argfile_basic_001); - SUITE_ADD_TEST(suite, test_argfile_basic_002); - SUITE_ADD_TEST(suite, test_argfile_basic_003); - SUITE_ADD_TEST(suite, test_argfile_basic_004); - SUITE_ADD_TEST(suite, test_argfile_basic_005); - SUITE_ADD_TEST(suite, test_argfile_basic_006); - SUITE_ADD_TEST(suite, test_argfile_basic_007); - SUITE_ADD_TEST(suite, test_argfile_basic_008); - SUITE_ADD_TEST(suite, test_argfile_basic_009); - SUITE_ADD_TEST(suite, test_argfile_basic_010); - SUITE_ADD_TEST(suite, test_argfile_basic_011); - SUITE_ADD_TEST(suite, test_argfile_basic_012); - SUITE_ADD_TEST(suite, test_argfile_basic_013); - SUITE_ADD_TEST(suite, test_argfile_basic_014); - SUITE_ADD_TEST(suite, test_argfile_basic_015); - SUITE_ADD_TEST(suite, test_argfile_basic_016); - SUITE_ADD_TEST(suite, test_argfile_basic_017); - SUITE_ADD_TEST(suite, test_argfile_basic_018); - SUITE_ADD_TEST(suite, test_argfile_basic_019); - SUITE_ADD_TEST(suite, test_argfile_basic_020); - SUITE_ADD_TEST(suite, test_argfile_basic_021); - SUITE_ADD_TEST(suite, test_argfile_basic_022); - SUITE_ADD_TEST(suite, test_argfile_basic_023); - SUITE_ADD_TEST(suite, test_argfile_basic_024); - SUITE_ADD_TEST(suite, test_argfile_basic_025); - SUITE_ADD_TEST(suite, test_argfile_basic_026); - SUITE_ADD_TEST(suite, test_argfile_basic_027); - SUITE_ADD_TEST(suite, test_argfile_basic_028); - SUITE_ADD_TEST(suite, test_argfile_basic_029); - SUITE_ADD_TEST(suite, test_argfile_basic_030); - SUITE_ADD_TEST(suite, test_argfile_basic_031); - SUITE_ADD_TEST(suite, test_argfile_basic_032); -#ifdef WIN32 - SUITE_ADD_TEST(suite, test_argfile_basic_033); - SUITE_ADD_TEST(suite, test_argfile_basic_034); -#endif /* #ifdef WIN32 */ - - return suite; -} - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/tests/testarghashtable.c b/parallel/parallel_src/extern/argtable3-3.2.2/tests/testarghashtable.c deleted file mode 100644 index 36e4a7d5..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/tests/testarghashtable.c +++ /dev/null @@ -1,276 +0,0 @@ -/******************************************************************************* - * This file is part of the argtable3 library. - * - * Copyright (C) 2013-2019 Tom G. Huang - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include -#include - -#include - -#include "CuTest.h" -#include "argtable3_private.h" - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable : 4204) -#pragma warning(disable : 4996) -#endif - -static unsigned int hash_key(const void* key) { - char* str = (char*)key; - int c; - unsigned int hash = 5381; - - while ((c = *str++) != 0) - hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ - - return hash; -} - -static int equal_keys(const void* key1, const void* key2) { - char* k1 = (char*)key1; - char* k2 = (char*)key2; - return (0 == strcmp(k1, k2)); -} - -void test_arghashtable_basic_001(CuTest* tc) { - arg_hashtable_t* h = arg_hashtable_create(32, hash_key, equal_keys); - CuAssertTrue(tc, h != 0); - CuAssertIntEquals(tc, arg_hashtable_count(h), 0); - - arg_hashtable_destroy(h, 1); -} - -void test_arghashtable_basic_002(CuTest* tc) { - arg_hashtable_t* h = arg_hashtable_create(32, hash_key, equal_keys); - CuAssertTrue(tc, h != 0); - CuAssertIntEquals(tc, arg_hashtable_count(h), 0); - - char* key_1 = "k1"; - char* k_1 = (char*)malloc(strlen(key_1) + 1); - memset(k_1, 0, strlen(key_1) + 1); - strncpy(k_1, key_1, strlen(key_1)); - - char* value_1 = "v1"; - char* v_1 = (char*)malloc(strlen(value_1) + 1); - memset(v_1, 0, strlen(value_1) + 1); - strncpy(v_1, value_1, strlen(value_1)); - - arg_hashtable_insert(h, k_1, v_1); - CuAssertIntEquals(tc, 1, arg_hashtable_count(h)); - - arg_hashtable_itr_t* itr = arg_hashtable_itr_create(h); - CuAssertTrue(tc, itr != 0); - CuAssertPtrEquals(tc, k_1, arg_hashtable_itr_key(itr)); - CuAssertTrue(tc, strcmp((char*)arg_hashtable_itr_key(itr), key_1) == 0); - CuAssertPtrEquals(tc, v_1, arg_hashtable_itr_value(itr)); - CuAssertTrue(tc, strcmp((char*)arg_hashtable_itr_value(itr), value_1) == 0); - - arg_hashtable_itr_destroy(itr); - arg_hashtable_destroy(h, 1); -} - -void test_arghashtable_basic_003(CuTest* tc) { - arg_hashtable_t* h = arg_hashtable_create(32, hash_key, equal_keys); - CuAssertTrue(tc, h != 0); - CuAssertIntEquals(tc, arg_hashtable_count(h), 0); - - char* key_1 = "k1"; - char* k_1 = (char*)malloc(strlen(key_1) + 1); - memset(k_1, 0, strlen(key_1) + 1); - strncpy(k_1, key_1, strlen(key_1)); - - char* value_1 = "v1"; - char* v_1 = (char*)malloc(strlen(value_1) + 1); - memset(v_1, 0, strlen(value_1) + 1); - strncpy(v_1, value_1, strlen(value_1)); - - arg_hashtable_insert(h, k_1, v_1); - CuAssertIntEquals(tc, 1, arg_hashtable_count(h)); - - char* key_2 = "k2"; - char* k_2 = (char*)malloc(strlen(key_2) + 1); - memset(k_2, 0, strlen(key_2) + 1); - strncpy(k_2, key_2, strlen(key_2)); - - char* value_2 = "v2"; - char* v_2 = (char*)malloc(strlen(value_2) + 1); - memset(v_2, 0, strlen(value_2) + 1); - strncpy(v_2, value_2, strlen(value_2)); - - arg_hashtable_insert(h, k_2, v_2); - CuAssertIntEquals(tc, 2, arg_hashtable_count(h)); - - arg_hashtable_itr_t* itr = arg_hashtable_itr_create(h); - CuAssertTrue(tc, itr != 0); - - int ret = arg_hashtable_itr_advance(itr); - CuAssertTrue(tc, ret != 0); - - ret = arg_hashtable_itr_advance(itr); - CuAssertTrue(tc, ret == 0); - - arg_hashtable_itr_destroy(itr); - arg_hashtable_destroy(h, 1); -} - -void test_arghashtable_basic_004(CuTest* tc) { - arg_hashtable_t* h = arg_hashtable_create(32, hash_key, equal_keys); - CuAssertTrue(tc, h != 0); - CuAssertIntEquals(tc, arg_hashtable_count(h), 0); - - char* key_1 = "k1"; - char* k_1 = (char*)malloc(strlen(key_1) + 1); - memset(k_1, 0, strlen(key_1) + 1); - strncpy(k_1, key_1, strlen(key_1)); - - char* value_1 = "v1"; - char* v_1 = (char*)malloc(strlen(value_1) + 1); - memset(v_1, 0, strlen(value_1) + 1); - strncpy(v_1, value_1, strlen(value_1)); - - arg_hashtable_insert(h, k_1, v_1); - CuAssertTrue(tc, h != 0); - CuAssertIntEquals(tc, 1, arg_hashtable_count(h)); - - arg_hashtable_itr_t* itr = arg_hashtable_itr_create(h); - int ret = arg_hashtable_itr_remove(itr); - CuAssertTrue(tc, ret == 0); - CuAssertIntEquals(tc, 0, arg_hashtable_count(h)); - - arg_hashtable_itr_destroy(itr); - arg_hashtable_destroy(h, 1); -} - -void test_arghashtable_basic_005(CuTest* tc) { - arg_hashtable_t* h = arg_hashtable_create(3, hash_key, equal_keys); - CuAssertTrue(tc, h != 0); - CuAssertIntEquals(tc, arg_hashtable_count(h), 0); - - char* key_1 = "k1"; - char* k_1 = (char*)malloc(strlen(key_1) + 1); - memset(k_1, 0, strlen(key_1) + 1); - strncpy(k_1, key_1, strlen(key_1)); - - char* value_1 = "v1"; - char* v_1 = (char*)malloc(strlen(value_1) + 1); - memset(v_1, 0, strlen(value_1) + 1); - strncpy(v_1, value_1, strlen(value_1)); - - arg_hashtable_insert(h, k_1, v_1); - CuAssertTrue(tc, h != 0); - CuAssertIntEquals(tc, 1, arg_hashtable_count(h)); - - arg_hashtable_remove(h, k_1); - CuAssertIntEquals(tc, 0, arg_hashtable_count(h)); - - arg_hashtable_destroy(h, 1); -} - -void test_arghashtable_basic_006(CuTest* tc) { - arg_hashtable_t* h = arg_hashtable_create(32, hash_key, equal_keys); - CuAssertTrue(tc, h != 0); - CuAssertIntEquals(tc, arg_hashtable_count(h), 0); - - char* key_1 = "k1"; - char* k_1 = (char*)malloc(strlen(key_1) + 1); - memset(k_1, 0, strlen(key_1) + 1); - strncpy(k_1, key_1, strlen(key_1)); - - char* value_1 = "v1"; - char* v_1 = (char*)malloc(strlen(value_1) + 1); - memset(v_1, 0, strlen(value_1) + 1); - strncpy(v_1, value_1, strlen(value_1)); - - arg_hashtable_insert(h, k_1, v_1); - CuAssertTrue(tc, arg_hashtable_count(h) == 1); - - char* vv = (char*)arg_hashtable_search(h, k_1); - CuAssertTrue(tc, strcmp(vv, v_1) == 0); - - arg_hashtable_destroy(h, 1); -} - -void test_arghashtable_basic_007(CuTest* tc) { - arg_hashtable_t* h = arg_hashtable_create(32, hash_key, equal_keys); - CuAssertTrue(tc, h != 0); - CuAssertIntEquals(tc, arg_hashtable_count(h), 0); - - char* key_1 = "k1"; - char* k_1 = (char*)malloc(strlen(key_1) + 1); - memset(k_1, 0, strlen(key_1) + 1); - strncpy(k_1, key_1, strlen(key_1)); - - char* value_1 = "v1"; - char* v_1 = (char*)malloc(strlen(value_1) + 1); - memset(v_1, 0, strlen(value_1) + 1); - strncpy(v_1, value_1, strlen(value_1)); - - arg_hashtable_insert(h, k_1, v_1); - CuAssertIntEquals(tc, 1, arg_hashtable_count(h)); - - char* key_2 = "k2"; - char* k_2 = (char*)malloc(strlen(key_2) + 1); - memset(k_2, 0, strlen(key_2) + 1); - strncpy(k_2, key_2, strlen(key_2)); - - char* value_2 = "v2"; - char* v_2 = (char*)malloc(strlen(value_2) + 1); - memset(v_2, 0, strlen(value_2) + 1); - strncpy(v_2, value_2, strlen(value_2)); - - arg_hashtable_insert(h, k_2, v_2); - CuAssertIntEquals(tc, 2, arg_hashtable_count(h)); - - arg_hashtable_itr_t itr; - int ret = arg_hashtable_itr_search(&itr, h, k_1); - CuAssertTrue(tc, ret != 0); - CuAssertPtrEquals(tc, k_1, arg_hashtable_itr_key(&itr)); - CuAssertPtrEquals(tc, v_1, arg_hashtable_itr_value(&itr)); - CuAssertTrue(tc, strcmp((char*)arg_hashtable_itr_key(&itr), k_1) == 0); - CuAssertTrue(tc, strcmp((char*)arg_hashtable_itr_value(&itr), v_1) == 0); - - arg_hashtable_destroy(h, 1); -} - -CuSuite* get_arghashtable_testsuite() { - CuSuite* suite = CuSuiteNew(); - SUITE_ADD_TEST(suite, test_arghashtable_basic_001); - SUITE_ADD_TEST(suite, test_arghashtable_basic_002); - SUITE_ADD_TEST(suite, test_arghashtable_basic_003); - SUITE_ADD_TEST(suite, test_arghashtable_basic_004); - SUITE_ADD_TEST(suite, test_arghashtable_basic_005); - SUITE_ADD_TEST(suite, test_arghashtable_basic_006); - SUITE_ADD_TEST(suite, test_arghashtable_basic_007); - return suite; -} - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/tests/testargint.c b/parallel/parallel_src/extern/argtable3-3.2.2/tests/testargint.c deleted file mode 100644 index 9bbac4e4..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/tests/testargint.c +++ /dev/null @@ -1,2018 +0,0 @@ -/******************************************************************************* - * This file is part of the argtable3 library. - * - * Copyright (C) 2013-2019 Tom G. Huang - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include - -#include "CuTest.h" -#include "argtable3.h" - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable : 4204) -#endif - -void test_argint_basic_001(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - - char* argv[] = {"program", "0", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertIntEquals(tc, a->ival[0], 0); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 0); - CuAssertTrue(tc, d->count == 0); - CuAssertTrue(tc, e->count == 0); - CuAssertTrue(tc, f->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_002(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - - char* argv[] = {"program", "1", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertIntEquals(tc, a->ival[0], 1); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 0); - CuAssertTrue(tc, d->count == 0); - CuAssertTrue(tc, e->count == 0); - CuAssertTrue(tc, f->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_003(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - - char* argv[] = {"program", "1", "2", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertIntEquals(tc, a->ival[0], 1); - CuAssertTrue(tc, b->count == 1); - CuAssertIntEquals(tc, b->ival[0], 2); - CuAssertTrue(tc, c->count == 0); - CuAssertTrue(tc, d->count == 0); - CuAssertTrue(tc, e->count == 0); - CuAssertTrue(tc, f->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_004(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - - char* argv[] = {"program", "5", "7", "9", "-d", "-21", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertIntEquals(tc, a->ival[0], 5); - CuAssertTrue(tc, b->count == 1); - CuAssertIntEquals(tc, b->ival[0], 7); - CuAssertTrue(tc, c->count == 1); - CuAssertIntEquals(tc, c->ival[0], 9); - CuAssertTrue(tc, d->count == 1); - CuAssertIntEquals(tc, d->ival[0], -21); - CuAssertTrue(tc, e->count == 0); - CuAssertTrue(tc, f->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_005(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - - char* argv[] = {"program", "-d", "1", "-D2", "--delta", "3", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 1); - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 0); - CuAssertTrue(tc, d->count == 3); - CuAssertIntEquals(tc, d->ival[0], 1); - CuAssertIntEquals(tc, d->ival[1], 2); - CuAssertIntEquals(tc, d->ival[2], 3); - CuAssertTrue(tc, e->count == 0); - CuAssertTrue(tc, f->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_006(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - - char* argv[] = {"program", "1", "2", "4", "--eps", "-7", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertIntEquals(tc, a->ival[0], 1); - CuAssertTrue(tc, b->count == 1); - CuAssertIntEquals(tc, b->ival[0], 2); - CuAssertTrue(tc, c->count == 1); - CuAssertIntEquals(tc, c->ival[0], 4); - CuAssertTrue(tc, d->count == 0); - CuAssertTrue(tc, e->count == 1); - CuAssertIntEquals(tc, e->ival[0], -7); - CuAssertTrue(tc, f->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_007(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - - char* argv[] = {"program", "1", "2", "4", "--eqn", "-7", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertIntEquals(tc, a->ival[0], 1); - CuAssertTrue(tc, b->count == 1); - CuAssertIntEquals(tc, b->ival[0], 2); - CuAssertTrue(tc, c->count == 1); - CuAssertIntEquals(tc, c->ival[0], 4); - CuAssertTrue(tc, d->count == 0); - CuAssertTrue(tc, e->count == 1); - CuAssertIntEquals(tc, e->ival[0], -7); - CuAssertTrue(tc, f->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_008(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - - char* argv[] = {"program", "1", "2", "3", "-D4", "--eps", "-10", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertIntEquals(tc, a->ival[0], 1); - CuAssertTrue(tc, b->count == 1); - CuAssertIntEquals(tc, b->ival[0], 2); - CuAssertTrue(tc, c->count == 1); - CuAssertIntEquals(tc, c->ival[0], 3); - CuAssertTrue(tc, d->count == 1); - CuAssertIntEquals(tc, d->ival[0], 4); - CuAssertTrue(tc, e->count == 1); - CuAssertIntEquals(tc, e->ival[0], -10); - CuAssertTrue(tc, f->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_009(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "1", "-f", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 0); - CuAssertTrue(tc, d->count == 0); - CuAssertTrue(tc, e->count == 0); - CuAssertTrue(tc, f->count == 1); - CuAssertIntEquals(tc, f->ival[0], -1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_010(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "-f", "1", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 0); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 1); - CuAssertIntEquals(tc, b->count, 0); - CuAssertIntEquals(tc, c->count, 0); - CuAssertIntEquals(tc, d->count, 0); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 1); - CuAssertIntEquals(tc, f->ival[0], -1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_011(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "-f", "2", "--filler", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 0); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 2); - CuAssertIntEquals(tc, b->count, 0); - CuAssertIntEquals(tc, c->count, 0); - CuAssertIntEquals(tc, d->count, 0); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 2); - CuAssertIntEquals(tc, f->ival[0], -1); - CuAssertIntEquals(tc, f->ival[1], -1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_012(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "-f", "1", "--filler=2", "-f", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 0); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 1); - CuAssertIntEquals(tc, b->count, 0); - CuAssertIntEquals(tc, c->count, 0); - CuAssertIntEquals(tc, d->count, 0); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 3); - CuAssertIntEquals(tc, f->ival[0], -1); - CuAssertIntEquals(tc, f->ival[1], 2); - CuAssertIntEquals(tc, f->ival[0], -1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_013(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "0x0", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 0); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 0x0); - CuAssertIntEquals(tc, b->count, 0); - CuAssertIntEquals(tc, c->count, 0); - CuAssertIntEquals(tc, d->count, 0); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_014(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "0x0", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 0); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 0x0); - CuAssertIntEquals(tc, b->count, 0); - CuAssertIntEquals(tc, c->count, 0); - CuAssertIntEquals(tc, d->count, 0); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_015(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "0x10", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 0); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 0x10); - CuAssertIntEquals(tc, b->count, 0); - CuAssertIntEquals(tc, c->count, 0); - CuAssertIntEquals(tc, d->count, 0); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_016(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "0x10", "0x32", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 0); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 0x10); - CuAssertIntEquals(tc, b->count, 1); - CuAssertIntEquals(tc, b->ival[0], 0x32); - CuAssertIntEquals(tc, c->count, 0); - CuAssertIntEquals(tc, d->count, 0); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_017(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "0x5", "0xA", "0xF", "-d", "-0x1E", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 0); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 0x5); - CuAssertIntEquals(tc, b->count, 1); - CuAssertIntEquals(tc, b->ival[0], 0xA); - CuAssertIntEquals(tc, c->count, 1); - CuAssertIntEquals(tc, c->ival[0], 0xF); - CuAssertIntEquals(tc, d->count, 1); - CuAssertIntEquals(tc, d->ival[0], -0x1E); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_018(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "-d", "0xab", "-D0x09", "--delta", "0x02e", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 1); - CuAssertIntEquals(tc, a->count, 0); - CuAssertIntEquals(tc, b->count, 0); - CuAssertIntEquals(tc, c->count, 0); - CuAssertIntEquals(tc, d->count, 3); - CuAssertIntEquals(tc, d->ival[0], 0xab); - CuAssertIntEquals(tc, d->ival[1], 0x09); - CuAssertIntEquals(tc, d->ival[2], 0x02e); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_019(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "0o0", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 0); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 0); - CuAssertIntEquals(tc, b->count, 0); - CuAssertIntEquals(tc, c->count, 0); - CuAssertIntEquals(tc, d->count, 0); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_020(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "0o10", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 0); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 010); - CuAssertIntEquals(tc, b->count, 0); - CuAssertIntEquals(tc, c->count, 0); - CuAssertIntEquals(tc, d->count, 0); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_021(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "0o67", "0O23", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 0); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 067); - CuAssertIntEquals(tc, b->count, 1); - CuAssertIntEquals(tc, b->ival[0], 023); - CuAssertIntEquals(tc, c->count, 0); - CuAssertIntEquals(tc, d->count, 0); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_022(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "0o5", "0O0", "0x1", "-d", "-0o6", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 0); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 05); - CuAssertIntEquals(tc, b->count, 1); - CuAssertIntEquals(tc, b->ival[0], 0); - CuAssertIntEquals(tc, c->count, 1); - CuAssertIntEquals(tc, c->ival[0], 0x1); - CuAssertIntEquals(tc, d->count, 1); - CuAssertIntEquals(tc, d->ival[0], -06); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_023(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "-d", "0o012", "-D0o0777", "--delta", "0o56", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 1); - CuAssertIntEquals(tc, a->count, 0); - CuAssertIntEquals(tc, b->count, 0); - CuAssertIntEquals(tc, c->count, 0); - CuAssertIntEquals(tc, d->count, 3); - CuAssertIntEquals(tc, d->ival[0], 012); - CuAssertIntEquals(tc, d->ival[1], 0777); - CuAssertIntEquals(tc, d->ival[2], 056); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_024(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "0B0", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 0); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 0); - CuAssertIntEquals(tc, b->count, 0); - CuAssertIntEquals(tc, c->count, 0); - CuAssertIntEquals(tc, d->count, 0); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_025(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "0B0", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 0); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 0); - CuAssertIntEquals(tc, b->count, 0); - CuAssertIntEquals(tc, c->count, 0); - CuAssertIntEquals(tc, d->count, 0); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_026(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "0b10", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 0); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 2); - CuAssertIntEquals(tc, b->count, 0); - CuAssertIntEquals(tc, c->count, 0); - CuAssertIntEquals(tc, d->count, 0); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_027(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "0B10110", "0b111001", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 0); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 22); - CuAssertIntEquals(tc, b->count, 1); - CuAssertIntEquals(tc, b->ival[0], 57); - CuAssertIntEquals(tc, c->count, 0); - CuAssertIntEquals(tc, d->count, 0); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_028(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "0B10110", "0b111001", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 0); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 22); - CuAssertIntEquals(tc, b->count, 1); - CuAssertIntEquals(tc, b->ival[0], 57); - CuAssertIntEquals(tc, c->count, 0); - CuAssertIntEquals(tc, d->count, 0); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_029(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "0b101001", "0b101", "0b00101010101", "-d", "0B110000011", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 0); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 41); - CuAssertIntEquals(tc, b->count, 1); - CuAssertIntEquals(tc, b->ival[0], 5); - CuAssertIntEquals(tc, c->count, 1); - CuAssertIntEquals(tc, c->ival[0], 341); - CuAssertIntEquals(tc, d->count, 1); - CuAssertIntEquals(tc, d->ival[0], 387); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_030(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "-d", "0b101", "-D0B11", "--delta", "0b11011", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 1); - CuAssertIntEquals(tc, a->count, 0); - CuAssertIntEquals(tc, b->count, 0); - CuAssertIntEquals(tc, c->count, 0); - CuAssertIntEquals(tc, d->count, 3); - CuAssertIntEquals(tc, d->ival[0], 5); - CuAssertIntEquals(tc, d->ival[1], 3); - CuAssertIntEquals(tc, d->ival[2], 27); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_031(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "11", "0x11", "0o11", "-D0b11", "--eps", "-0o50", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 0); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 11); - CuAssertIntEquals(tc, b->count, 1); - CuAssertIntEquals(tc, b->ival[0], 0x11); - CuAssertIntEquals(tc, c->count, 1); - CuAssertIntEquals(tc, c->ival[0], 011); - CuAssertIntEquals(tc, d->count, 1); - CuAssertIntEquals(tc, d->ival[0], 3); - CuAssertIntEquals(tc, e->count, 1); - CuAssertIntEquals(tc, e->ival[0], -050); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_032(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "1KB", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 0); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 1024); - CuAssertIntEquals(tc, b->count, 0); - CuAssertIntEquals(tc, c->count, 0); - CuAssertIntEquals(tc, d->count, 0); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_033(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "1MB", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 0); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 1024 * 1024); - CuAssertIntEquals(tc, b->count, 0); - CuAssertIntEquals(tc, c->count, 0); - CuAssertIntEquals(tc, d->count, 0); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_034(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "1GB", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 0); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 1024 * 1024 * 1024); - CuAssertIntEquals(tc, b->count, 0); - CuAssertIntEquals(tc, c->count, 0); - CuAssertIntEquals(tc, d->count, 0); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_035(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "0x5KB", "0xAMB", "0x1GB", "-d", "-0x40A01400", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 0); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 0x5 * 1024); - CuAssertIntEquals(tc, b->count, 1); - CuAssertIntEquals(tc, b->ival[0], 0xA * 1024 * 1024); - CuAssertIntEquals(tc, c->count, 1); - CuAssertIntEquals(tc, c->ival[0], 0x1 * 1024 * 1024 * 1024); - CuAssertIntEquals(tc, d->count, 1); - CuAssertIntEquals(tc, d->ival[0], -0x40A01400); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_036(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 1); - CuAssertIntEquals(tc, a->count, 0); - CuAssertIntEquals(tc, b->count, 0); - CuAssertIntEquals(tc, c->count, 0); - CuAssertIntEquals(tc, d->count, 0); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_037(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "1", "2", "3", "4", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 1); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 1); - CuAssertIntEquals(tc, b->count, 1); - CuAssertIntEquals(tc, b->ival[0], 2); - CuAssertIntEquals(tc, c->count, 1); - CuAssertIntEquals(tc, c->ival[0], 3); - CuAssertIntEquals(tc, d->count, 0); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_038(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "1", "2", "3", "-d1", "-d2", "-d3", "-d4", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 1); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 1); - CuAssertIntEquals(tc, b->count, 1); - CuAssertIntEquals(tc, b->ival[0], 2); - CuAssertIntEquals(tc, c->count, 1); - CuAssertIntEquals(tc, c->ival[0], 3); - CuAssertIntEquals(tc, d->count, 3); - CuAssertIntEquals(tc, d->ival[0], 1); - CuAssertIntEquals(tc, d->ival[1], 2); - CuAssertIntEquals(tc, d->ival[2], 3); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_039(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "1", "2", "3", "-d1", "-d2", "-d3", "-d", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 1); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 1); - CuAssertIntEquals(tc, b->count, 1); - CuAssertIntEquals(tc, b->ival[0], 2); - CuAssertIntEquals(tc, c->count, 1); - CuAssertIntEquals(tc, c->ival[0], 3); - CuAssertIntEquals(tc, d->count, 3); - CuAssertIntEquals(tc, d->ival[0], 1); - CuAssertIntEquals(tc, d->ival[1], 2); - CuAssertIntEquals(tc, d->ival[2], 3); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_040(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "1", "2", "3", "-d1", "-d2", "-d", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 1); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 1); - CuAssertIntEquals(tc, b->count, 1); - CuAssertIntEquals(tc, b->ival[0], 2); - CuAssertIntEquals(tc, c->count, 1); - CuAssertIntEquals(tc, c->ival[0], 3); - CuAssertIntEquals(tc, d->count, 2); - CuAssertIntEquals(tc, d->ival[0], 1); - CuAssertIntEquals(tc, d->ival[1], 2); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_041(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "1", "2", "3", "-d1", "-d", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 1); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 1); - CuAssertIntEquals(tc, b->count, 1); - CuAssertIntEquals(tc, b->ival[0], 2); - CuAssertIntEquals(tc, c->count, 1); - CuAssertIntEquals(tc, c->ival[0], 3); - CuAssertIntEquals(tc, d->count, 1); - CuAssertIntEquals(tc, d->ival[0], 1); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_042(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "1", "2", "3", "-d", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 1); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 1); - CuAssertIntEquals(tc, b->count, 1); - CuAssertIntEquals(tc, b->ival[0], 2); - CuAssertIntEquals(tc, c->count, 1); - CuAssertIntEquals(tc, c->ival[0], 3); - CuAssertIntEquals(tc, d->count, 0); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_043(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "1", "2", "3", "--eps", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_044(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "1", "2", "3", "--eps", "3", "--eqn", "6", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 1); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 1); - CuAssertIntEquals(tc, b->count, 1); - CuAssertIntEquals(tc, b->ival[0], 2); - CuAssertIntEquals(tc, c->count, 1); - CuAssertIntEquals(tc, c->ival[0], 3); - CuAssertIntEquals(tc, d->count, 0); - CuAssertIntEquals(tc, e->count, 1); - CuAssertIntEquals(tc, e->ival[0], 3); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_045(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "hello", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 1); - CuAssertIntEquals(tc, a->count, 0); - CuAssertIntEquals(tc, b->count, 0); - CuAssertIntEquals(tc, c->count, 0); - CuAssertIntEquals(tc, d->count, 0); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_046(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "1.234", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 1); - CuAssertIntEquals(tc, a->count, 0); - CuAssertIntEquals(tc, b->count, 0); - CuAssertIntEquals(tc, c->count, 0); - CuAssertIntEquals(tc, d->count, 0); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_047(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "4", "hello", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 1); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 4); - CuAssertIntEquals(tc, b->count, 0); - CuAssertIntEquals(tc, c->count, 0); - CuAssertIntEquals(tc, d->count, 0); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_048(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "5", "1.234", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 1); - CuAssertIntEquals(tc, a->count, 1); - CuAssertIntEquals(tc, a->ival[0], 5); - CuAssertIntEquals(tc, b->count, 0); - CuAssertIntEquals(tc, c->count, 0); - CuAssertIntEquals(tc, d->count, 0); - CuAssertIntEquals(tc, e->count, 0); - CuAssertIntEquals(tc, f->count, 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_049(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "-f", "2", "--filler=", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 2); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_050(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "0x0g", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_051(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "0o08", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_052(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "0b02", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_053(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "1000GB", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argint_basic_054(CuTest* tc) { - struct arg_int* a = arg_int1(NULL, NULL, "a", "a is "); - struct arg_int* b = arg_int0(NULL, NULL, "b", "b is "); - struct arg_int* c = arg_int0(NULL, NULL, "c", "c is "); - struct arg_int* d = arg_intn("dD", "delta", "", 0, 3, "d can occur 0..3 times"); - struct arg_int* e = arg_int0(NULL, "eps,eqn", "", "eps is optional"); - struct arg_int* f = arg_intn("fF", "filler", "", 0, 3, "f can occur 0..3 times"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, e, f, end}; - int nerrors; - int i; - - char* argv[] = {"program", "1GBH", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - /* allow missing argument values for the f argument, and set defaults to -1 */ - f->hdr.flag |= ARG_HASOPTVALUE; - for (i = 0; i < f->hdr.maxcount; i++) - f->ival[i] = -1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertIntEquals(tc, nerrors, 1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -CuSuite* get_argint_testsuite() { - CuSuite* suite = CuSuiteNew(); - SUITE_ADD_TEST(suite, test_argint_basic_001); - SUITE_ADD_TEST(suite, test_argint_basic_002); - SUITE_ADD_TEST(suite, test_argint_basic_003); - SUITE_ADD_TEST(suite, test_argint_basic_004); - SUITE_ADD_TEST(suite, test_argint_basic_005); - SUITE_ADD_TEST(suite, test_argint_basic_006); - SUITE_ADD_TEST(suite, test_argint_basic_007); - SUITE_ADD_TEST(suite, test_argint_basic_008); - SUITE_ADD_TEST(suite, test_argint_basic_009); - SUITE_ADD_TEST(suite, test_argint_basic_010); - SUITE_ADD_TEST(suite, test_argint_basic_011); - SUITE_ADD_TEST(suite, test_argint_basic_012); - SUITE_ADD_TEST(suite, test_argint_basic_013); - SUITE_ADD_TEST(suite, test_argint_basic_014); - SUITE_ADD_TEST(suite, test_argint_basic_015); - SUITE_ADD_TEST(suite, test_argint_basic_016); - SUITE_ADD_TEST(suite, test_argint_basic_017); - SUITE_ADD_TEST(suite, test_argint_basic_018); - SUITE_ADD_TEST(suite, test_argint_basic_019); - SUITE_ADD_TEST(suite, test_argint_basic_020); - SUITE_ADD_TEST(suite, test_argint_basic_021); - SUITE_ADD_TEST(suite, test_argint_basic_022); - SUITE_ADD_TEST(suite, test_argint_basic_023); - SUITE_ADD_TEST(suite, test_argint_basic_024); - SUITE_ADD_TEST(suite, test_argint_basic_025); - SUITE_ADD_TEST(suite, test_argint_basic_026); - SUITE_ADD_TEST(suite, test_argint_basic_027); - SUITE_ADD_TEST(suite, test_argint_basic_028); - SUITE_ADD_TEST(suite, test_argint_basic_029); - SUITE_ADD_TEST(suite, test_argint_basic_030); - SUITE_ADD_TEST(suite, test_argint_basic_031); - SUITE_ADD_TEST(suite, test_argint_basic_032); - SUITE_ADD_TEST(suite, test_argint_basic_033); - SUITE_ADD_TEST(suite, test_argint_basic_034); - SUITE_ADD_TEST(suite, test_argint_basic_035); - SUITE_ADD_TEST(suite, test_argint_basic_036); - SUITE_ADD_TEST(suite, test_argint_basic_037); - SUITE_ADD_TEST(suite, test_argint_basic_038); - SUITE_ADD_TEST(suite, test_argint_basic_039); - SUITE_ADD_TEST(suite, test_argint_basic_040); - SUITE_ADD_TEST(suite, test_argint_basic_041); - SUITE_ADD_TEST(suite, test_argint_basic_042); - SUITE_ADD_TEST(suite, test_argint_basic_043); - SUITE_ADD_TEST(suite, test_argint_basic_044); - SUITE_ADD_TEST(suite, test_argint_basic_045); - SUITE_ADD_TEST(suite, test_argint_basic_046); - SUITE_ADD_TEST(suite, test_argint_basic_047); - SUITE_ADD_TEST(suite, test_argint_basic_048); - SUITE_ADD_TEST(suite, test_argint_basic_049); - SUITE_ADD_TEST(suite, test_argint_basic_050); - SUITE_ADD_TEST(suite, test_argint_basic_051); - SUITE_ADD_TEST(suite, test_argint_basic_052); - SUITE_ADD_TEST(suite, test_argint_basic_053); - SUITE_ADD_TEST(suite, test_argint_basic_054); - return suite; -} - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/tests/testarglit.c b/parallel/parallel_src/extern/argtable3-3.2.2/tests/testarglit.c deleted file mode 100644 index 15ed6f1f..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/tests/testarglit.c +++ /dev/null @@ -1,538 +0,0 @@ -/******************************************************************************* - * This file is part of the argtable3 library. - * - * Copyright (C) 2013-2019 Tom G. Huang - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include - -#include "CuTest.h" -#include "argtable3.h" - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable : 4204) -#endif - -void test_arglit_basic_001(CuTest* tc) { - struct arg_lit* a = arg_lit0(NULL, "hello,world", "either --hello or --world or none"); - struct arg_lit* b = arg_lit0("bB", NULL, "either -b or -B or none"); - struct arg_lit* c = arg_lit1("cC", NULL, "either -c or -C"); - struct arg_lit* d = arg_litn("dD", "delta", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_lit* help = arg_lit0(NULL, "help", "print this help and exit"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, help, end}; - int nerrors; - - char* argv[] = {"program", "--help", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 2); - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 0); - CuAssertTrue(tc, d->count == 0); - CuAssertTrue(tc, help->count == 1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_arglit_basic_002(CuTest* tc) { - struct arg_lit* a = arg_lit0(NULL, "hello,world", "either --hello or --world or none"); - struct arg_lit* b = arg_lit0("bB", NULL, "either -b or -B or none"); - struct arg_lit* c = arg_lit1("cC", NULL, "either -c or -C"); - struct arg_lit* d = arg_litn("dD", "delta", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_lit* help = arg_lit0(NULL, "help", "print this help and exit"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, help, end}; - int nerrors; - - char* argv[] = {"program", "-cDd", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - if (nerrors > 0) - arg_print_errors(stdout, end, argv[0]); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 1); - CuAssertTrue(tc, d->count == 2); - CuAssertTrue(tc, help->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_arglit_basic_003(CuTest* tc) { - struct arg_lit* a = arg_lit0(NULL, "hello,world", "either --hello or --world or none"); - struct arg_lit* b = arg_lit0("bB", NULL, "either -b or -B or none"); - struct arg_lit* c = arg_lit1("cC", NULL, "either -c or -C"); - struct arg_lit* d = arg_litn("dD", "delta", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_lit* help = arg_lit0(NULL, "help", "print this help and exit"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, help, end}; - int nerrors; - - char* argv[] = {"program", "-cdDd", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - if (nerrors > 0) - arg_print_errors(stdout, end, argv[0]); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 1); - CuAssertTrue(tc, d->count == 3); - CuAssertTrue(tc, help->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_arglit_basic_004(CuTest* tc) { - struct arg_lit* a = arg_lit0(NULL, "hello,world", "either --hello or --world or none"); - struct arg_lit* b = arg_lit0("bB", NULL, "either -b or -B or none"); - struct arg_lit* c = arg_lit1("cC", NULL, "either -c or -C"); - struct arg_lit* d = arg_litn("dD", "delta", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_lit* help = arg_lit0(NULL, "help", "print this help and exit"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, help, end}; - int nerrors; - - char* argv[] = {"program", "-CDd", "--delta", "--delta", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - if (nerrors > 0) - arg_print_errors(stdout, end, argv[0]); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 1); - CuAssertTrue(tc, d->count == 4); - CuAssertTrue(tc, help->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_arglit_basic_005(CuTest* tc) { - struct arg_lit* a = arg_lit0(NULL, "hello,world", "either --hello or --world or none"); - struct arg_lit* b = arg_lit0("bB", NULL, "either -b or -B or none"); - struct arg_lit* c = arg_lit1("cC", NULL, "either -c or -C"); - struct arg_lit* d = arg_litn("dD", "delta", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_lit* help = arg_lit0(NULL, "help", "print this help and exit"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, help, end}; - int nerrors; - - char* argv[] = {"program", "--delta", "-cD", "-b", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - if (nerrors > 0) - arg_print_errors(stdout, end, argv[0]); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 1); - CuAssertTrue(tc, c->count == 1); - CuAssertTrue(tc, d->count == 2); - CuAssertTrue(tc, help->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_arglit_basic_006(CuTest* tc) { - struct arg_lit* a = arg_lit0(NULL, "hello,world", "either --hello or --world or none"); - struct arg_lit* b = arg_lit0("bB", NULL, "either -b or -B or none"); - struct arg_lit* c = arg_lit1("cC", NULL, "either -c or -C"); - struct arg_lit* d = arg_litn("dD", "delta", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_lit* help = arg_lit0(NULL, "help", "print this help and exit"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, help, end}; - int nerrors; - - char* argv[] = {"program", "-D", "-B", "--delta", "-C", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - if (nerrors > 0) - arg_print_errors(stdout, end, argv[0]); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 1); - CuAssertTrue(tc, c->count == 1); - CuAssertTrue(tc, d->count == 2); - CuAssertTrue(tc, help->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_arglit_basic_007(CuTest* tc) { - struct arg_lit* a = arg_lit0(NULL, "hello,world", "either --hello or --world or none"); - struct arg_lit* b = arg_lit0("bB", NULL, "either -b or -B or none"); - struct arg_lit* c = arg_lit1("cC", NULL, "either -c or -C"); - struct arg_lit* d = arg_litn("dD", "delta", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_lit* help = arg_lit0(NULL, "help", "print this help and exit"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, help, end}; - int nerrors; - - char* argv[] = {"program", "-D", "-B", "--delta", "-C", "--hello", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - if (nerrors > 0) - arg_print_errors(stdout, end, argv[0]); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertTrue(tc, b->count == 1); - CuAssertTrue(tc, c->count == 1); - CuAssertTrue(tc, d->count == 2); - CuAssertTrue(tc, help->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_arglit_basic_008(CuTest* tc) { - struct arg_lit* a = arg_lit0(NULL, "hello,world", "either --hello or --world or none"); - struct arg_lit* b = arg_lit0("bB", NULL, "either -b or -B or none"); - struct arg_lit* c = arg_lit1("cC", NULL, "either -c or -C"); - struct arg_lit* d = arg_litn("dD", "delta", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_lit* help = arg_lit0(NULL, "help", "print this help and exit"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, help, end}; - int nerrors; - - char* argv[] = {"program", "-D", "-B", "--delta", "-C", "--world", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - if (nerrors > 0) - arg_print_errors(stdout, end, argv[0]); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertTrue(tc, b->count == 1); - CuAssertTrue(tc, c->count == 1); - CuAssertTrue(tc, d->count == 2); - CuAssertTrue(tc, help->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_arglit_basic_009(CuTest* tc) { - struct arg_lit* a = arg_lit0(NULL, "hello,world", "either --hello or --world or none"); - struct arg_lit* b = arg_lit0("bB", NULL, "either -b or -B or none"); - struct arg_lit* c = arg_lit1("cC", NULL, "either -c or -C"); - struct arg_lit* d = arg_litn("dD", "delta", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_lit* help = arg_lit0(NULL, "help", "print this help and exit"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, help, end}; - int nerrors; - - char* argv[] = {"program", "-c", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 1); - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 1); - CuAssertTrue(tc, d->count == 0); - CuAssertTrue(tc, help->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_arglit_basic_010(CuTest* tc) { - struct arg_lit* a = arg_lit0(NULL, "hello,world", "either --hello or --world or none"); - struct arg_lit* b = arg_lit0("bB", NULL, "either -b or -B or none"); - struct arg_lit* c = arg_lit1("cC", NULL, "either -c or -C"); - struct arg_lit* d = arg_litn("dD", "delta", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_lit* help = arg_lit0(NULL, "help", "print this help and exit"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, help, end}; - int nerrors; - - char* argv[] = {"program", "-D", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 2); - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 0); - CuAssertTrue(tc, d->count == 1); - CuAssertTrue(tc, help->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_arglit_basic_011(CuTest* tc) { - struct arg_lit* a = arg_lit0(NULL, "hello,world", "either --hello or --world or none"); - struct arg_lit* b = arg_lit0("bB", NULL, "either -b or -B or none"); - struct arg_lit* c = arg_lit1("cC", NULL, "either -c or -C"); - struct arg_lit* d = arg_litn("dD", "delta", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_lit* help = arg_lit0(NULL, "help", "print this help and exit"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, help, end}; - int nerrors; - - char* argv[] = {"program", "-CD", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 1); - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 1); - CuAssertTrue(tc, d->count == 1); - CuAssertTrue(tc, help->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_arglit_basic_012(CuTest* tc) { - struct arg_lit* a = arg_lit0(NULL, "hello,world", "either --hello or --world or none"); - struct arg_lit* b = arg_lit0("bB", NULL, "either -b or -B or none"); - struct arg_lit* c = arg_lit1("cC", NULL, "either -c or -C"); - struct arg_lit* d = arg_litn("dD", "delta", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_lit* help = arg_lit0(NULL, "help", "print this help and exit"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, help, end}; - int nerrors; - - char* argv[] = {"program", "-Dd", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 1); - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 0); - CuAssertTrue(tc, d->count == 2); - CuAssertTrue(tc, help->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_arglit_basic_013(CuTest* tc) { - struct arg_lit* a = arg_lit0(NULL, "hello,world", "either --hello or --world or none"); - struct arg_lit* b = arg_lit0("bB", NULL, "either -b or -B or none"); - struct arg_lit* c = arg_lit1("cC", NULL, "either -c or -C"); - struct arg_lit* d = arg_litn("dD", "delta", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_lit* help = arg_lit0(NULL, "help", "print this help and exit"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, help, end}; - int nerrors; - - char* argv[] = {"program", "-cddddd", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 1); - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 1); - CuAssertTrue(tc, d->count == 4); - CuAssertTrue(tc, help->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_arglit_basic_014(CuTest* tc) { - struct arg_lit* a = arg_lit0(NULL, "hello,world", "either --hello or --world or none"); - struct arg_lit* b = arg_lit0("bB", NULL, "either -b or -B or none"); - struct arg_lit* c = arg_lit1("cC", NULL, "either -c or -C"); - struct arg_lit* d = arg_litn("dD", "delta", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_lit* help = arg_lit0(NULL, "help", "print this help and exit"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, help, end}; - int nerrors; - - char* argv[] = {"program", "-ccddd", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 1); - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 1); - CuAssertTrue(tc, d->count == 3); - CuAssertTrue(tc, help->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_arglit_basic_015(CuTest* tc) { - struct arg_lit* a = arg_lit0(NULL, "hello,world", "either --hello or --world or none"); - struct arg_lit* b = arg_lit0("bB", NULL, "either -b or -B or none"); - struct arg_lit* c = arg_lit1("cC", NULL, "either -c or -C"); - struct arg_lit* d = arg_litn("dD", "delta", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_lit* help = arg_lit0(NULL, "help", "print this help and exit"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, help, end}; - int nerrors; - - char* argv[] = {"program", "-C", "-d", "-D", "--delta", "-b", "-B", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 1); - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 1); - CuAssertTrue(tc, c->count == 1); - CuAssertTrue(tc, d->count == 3); - CuAssertTrue(tc, help->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_arglit_basic_016(CuTest* tc) { - struct arg_lit* a = arg_lit0(NULL, "hello,world", "either --hello or --world or none"); - struct arg_lit* b = arg_lit0("bB", NULL, "either -b or -B or none"); - struct arg_lit* c = arg_lit1("cC", NULL, "either -c or -C"); - struct arg_lit* d = arg_litn("dD", "delta", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_lit* help = arg_lit0(NULL, "help", "print this help and exit"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, help, end}; - int nerrors; - - char* argv[] = {"program", "-C", "-d", "-D", "--delta", "--hello", "--world", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 1); - CuAssertTrue(tc, a->count == 1); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 1); - CuAssertTrue(tc, d->count == 3); - CuAssertTrue(tc, help->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_arglit_basic_017(CuTest* tc) { - struct arg_lit* a = arg_lit0(NULL, "hello,world", "either --hello or --world or none"); - struct arg_lit* b = arg_lit0("bB", NULL, "either -b or -B or none"); - struct arg_lit* c = arg_lit1("cC", NULL, "either -c or -C"); - struct arg_lit* d = arg_litn("dD", "delta", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_lit* help = arg_lit0(NULL, "help", "print this help and exit"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, help, end}; - int nerrors; - - char* argv[] = {"program", "-C", "-d", "-D", "--delta", "--hello", "X", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 1); - CuAssertTrue(tc, a->count == 1); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 1); - CuAssertTrue(tc, d->count == 3); - CuAssertTrue(tc, help->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -CuSuite* get_arglit_testsuite() { - CuSuite* suite = CuSuiteNew(); - SUITE_ADD_TEST(suite, test_arglit_basic_001); - SUITE_ADD_TEST(suite, test_arglit_basic_002); - SUITE_ADD_TEST(suite, test_arglit_basic_003); - SUITE_ADD_TEST(suite, test_arglit_basic_004); - SUITE_ADD_TEST(suite, test_arglit_basic_005); - SUITE_ADD_TEST(suite, test_arglit_basic_006); - SUITE_ADD_TEST(suite, test_arglit_basic_007); - SUITE_ADD_TEST(suite, test_arglit_basic_008); - SUITE_ADD_TEST(suite, test_arglit_basic_009); - SUITE_ADD_TEST(suite, test_arglit_basic_010); - SUITE_ADD_TEST(suite, test_arglit_basic_011); - SUITE_ADD_TEST(suite, test_arglit_basic_012); - SUITE_ADD_TEST(suite, test_arglit_basic_013); - SUITE_ADD_TEST(suite, test_arglit_basic_014); - SUITE_ADD_TEST(suite, test_arglit_basic_015); - SUITE_ADD_TEST(suite, test_arglit_basic_016); - SUITE_ADD_TEST(suite, test_arglit_basic_017); - return suite; -} - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/tests/testargrex.c b/parallel/parallel_src/extern/argtable3-3.2.2/tests/testargrex.c deleted file mode 100644 index d3c3f7dd..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/tests/testargrex.c +++ /dev/null @@ -1,299 +0,0 @@ -/******************************************************************************* - * This file is part of the argtable3 library. - * - * Copyright (C) 2013-2019 Tom G. Huang - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include - -#include "CuTest.h" -#include "argtable3.h" - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable : 4204) -#endif - -void test_argrex_basic_001(CuTest* tc) { - struct arg_rex* a = arg_rex0("a", NULL, "hello", NULL, 0, "blah blah"); - struct arg_rex* b = arg_rex1(NULL, "beta", "[Ww]orld", NULL, 0, "blah blah"); - struct arg_rex* c = arg_rexn(NULL, NULL, "goodbye", NULL, 1, 5, ARG_REX_ICASE, "blah blah"); - struct arg_rex* d = arg_rex0(NULL, NULL, "any.*", NULL, ARG_REX_ICASE, "blah blah"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, end}; - int nerrors; - - char* argv[] = {"program", "--beta", "world", "goodbye", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 0); - - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 1); - CuAssertStrEquals(tc, b->sval[0], "world"); - CuAssertTrue(tc, c->count == 1); - CuAssertStrEquals(tc, c->sval[0], "goodbye"); - CuAssertTrue(tc, d->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argrex_basic_002(CuTest* tc) { - struct arg_rex* a = arg_rex0("a", NULL, "hello", NULL, 0, "blah blah"); - struct arg_rex* b = arg_rex1(NULL, "beta", "[Ww]orld", NULL, 0, "blah blah"); - struct arg_rex* c = arg_rexn(NULL, NULL, "goodbye", NULL, 1, 5, ARG_REX_ICASE, "blah blah"); - struct arg_rex* d = arg_rex0(NULL, NULL, "any.*", NULL, ARG_REX_ICASE, "blah blah"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, end}; - int nerrors; - - char* argv[] = {"program", "GoodBye", "--beta", "World", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 0); - - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 1); - CuAssertStrEquals(tc, b->sval[0], "World"); - CuAssertTrue(tc, c->count == 1); - CuAssertStrEquals(tc, c->sval[0], "GoodBye"); - CuAssertTrue(tc, d->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argrex_basic_003(CuTest* tc) { - struct arg_rex* a = arg_rex0("a", NULL, "hello", NULL, 0, "blah blah"); - struct arg_rex* b = arg_rex1(NULL, "beta", "[Ww]orld", NULL, 0, "blah blah"); - struct arg_rex* c = arg_rexn(NULL, NULL, "goodbye", NULL, 1, 5, ARG_REX_ICASE, "blah blah"); - struct arg_rex* d = arg_rex0(NULL, NULL, "any.*", NULL, ARG_REX_ICASE, "blah blah"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, end}; - int nerrors; - - char* argv[] = {"program", "--beta", "world", "GOODBYE", "GoodBye", "gOoDbyE", "Anything", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 0); - - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 1); - CuAssertStrEquals(tc, b->sval[0], "world"); - CuAssertTrue(tc, c->count == 3); - CuAssertStrEquals(tc, c->sval[0], "GOODBYE"); - CuAssertStrEquals(tc, c->sval[1], "GoodBye"); - CuAssertStrEquals(tc, c->sval[2], "gOoDbyE"); - CuAssertTrue(tc, d->count == 1); - CuAssertStrEquals(tc, d->sval[0], "Anything"); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argrex_basic_004(CuTest* tc) { - struct arg_rex* a = arg_rex0("a", NULL, "hello", NULL, 0, "blah blah"); - struct arg_rex* b = arg_rex1(NULL, "beta", "[Ww]orld", NULL, 0, "blah blah"); - struct arg_rex* c = arg_rexn(NULL, NULL, "goodbye", NULL, 1, 5, ARG_REX_ICASE, "blah blah"); - struct arg_rex* d = arg_rex0(NULL, NULL, "any.*", NULL, ARG_REX_ICASE, "blah blah"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, end}; - int nerrors; - - char* argv[] = {"program", "--beta", "world", "GOODBYE", "AnyHow", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 0); - - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 1); - CuAssertStrEquals(tc, b->sval[0], "world"); - CuAssertTrue(tc, c->count == 1); - CuAssertStrEquals(tc, c->sval[0], "GOODBYE"); - CuAssertTrue(tc, d->count == 1); - CuAssertStrEquals(tc, d->sval[0], "AnyHow"); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argrex_basic_005(CuTest* tc) { - struct arg_rex* a = arg_rex0("a", NULL, "hello", NULL, 0, "blah blah"); - struct arg_rex* b = arg_rex1(NULL, "beta", "[Ww]orld", NULL, 0, "blah blah"); - struct arg_rex* c = arg_rexn(NULL, NULL, "goodbye", NULL, 1, 5, ARG_REX_ICASE, "blah blah"); - struct arg_rex* d = arg_rex0(NULL, NULL, "any.*", NULL, ARG_REX_ICASE, "blah blah"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, end}; - int nerrors; - - char* argv[] = {"program", "-a", "hello", "--beta", "world", "GOODBYE", "AnyHow", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 0); - - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->sval[0], "hello"); - CuAssertTrue(tc, b->count == 1); - CuAssertStrEquals(tc, b->sval[0], "world"); - CuAssertTrue(tc, c->count == 1); - CuAssertStrEquals(tc, c->sval[0], "GOODBYE"); - CuAssertTrue(tc, d->count == 1); - CuAssertStrEquals(tc, d->sval[0], "AnyHow"); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argrex_basic_006(CuTest* tc) { - struct arg_rex* a = arg_rex0("a", NULL, "hello", NULL, 0, "blah blah"); - struct arg_rex* b = arg_rex1(NULL, "beta", "[Ww]orld", NULL, 0, "blah blah"); - struct arg_rex* c = arg_rexn(NULL, NULL, "goodbye", NULL, 1, 5, ARG_REX_ICASE, "blah blah"); - struct arg_rex* d = arg_rex0(NULL, NULL, "any.*", NULL, ARG_REX_ICASE, "blah blah"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, end}; - int nerrors; - - char* argv[] = {"program", "--beta", "WORLD", "goodbye", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argrex_basic_007(CuTest* tc) { - struct arg_rex* a = arg_rex0("a", NULL, "hello", NULL, 0, "blah blah"); - struct arg_rex* b = arg_rex1(NULL, "beta", "[Ww]orld", NULL, 0, "blah blah"); - struct arg_rex* c = arg_rexn(NULL, NULL, "goodbye", NULL, 1, 5, ARG_REX_ICASE, "blah blah"); - struct arg_rex* d = arg_rex0(NULL, NULL, "any.*", NULL, ARG_REX_ICASE, "blah blah"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, end}; - int nerrors; - - char* argv[] = {"program", "--beta", "World", "goodby", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argrex_basic_008(CuTest* tc) { - struct arg_rex* a = arg_rex0("a", NULL, "hello", NULL, 0, "blah blah"); - struct arg_rex* b = arg_rex1(NULL, "beta", "[Ww]orld", NULL, 0, "blah blah"); - struct arg_rex* c = arg_rexn(NULL, NULL, "goodbye", NULL, 1, 5, ARG_REX_ICASE, "blah blah"); - struct arg_rex* d = arg_rex0(NULL, NULL, "any.*", NULL, ARG_REX_ICASE, "blah blah"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, end}; - int nerrors; - - char* argv[] = {"program", "--beta", "world", "GoodBye", "Goodbye", "gOoDbyE", "Anything", "goodbye", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argrex_basic_009(CuTest* tc) { - struct arg_rex* a = arg_rex0("a", NULL, "hello", NULL, 0, "blah blah"); - struct arg_rex* b = arg_rex1(NULL, "beta", "[Ww]orld", NULL, 0, "blah blah"); - struct arg_rex* c = arg_rexn(NULL, NULL, "goodbye", NULL, 1, 5, ARG_REX_ICASE, "blah blah"); - struct arg_rex* d = arg_rex0(NULL, NULL, "any.*", NULL, ARG_REX_ICASE, "blah blah"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, end}; - int nerrors; - - char* argv[] = {"program", "--beta", "world", "GoodBye", "Goodbye", "gOoDbyE", "Anything", "Anytime", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argrex_basic_010(CuTest* tc) { - struct arg_rex* a = arg_rex0("a", NULL, "hello", NULL, 0, "blah blah"); - struct arg_rex* b = arg_rex1(NULL, "beta", "[Ww]orld", NULL, 0, "blah blah"); - struct arg_rex* c = arg_rexn(NULL, NULL, "goodbye", NULL, 1, 5, ARG_REX_ICASE, "blah blah"); - struct arg_rex* d = arg_rex0(NULL, NULL, "any.*", NULL, ARG_REX_ICASE, "blah blah"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, end}; - int nerrors; - - char* argv[] = {"program", "--beta", "world", "GoodBye", "Goodbye", "Anything", "-a", "Hello", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - CuAssertTrue(tc, nerrors == 1); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -CuSuite* get_argrex_testsuite() { - CuSuite* suite = CuSuiteNew(); - SUITE_ADD_TEST(suite, test_argrex_basic_001); - SUITE_ADD_TEST(suite, test_argrex_basic_002); - SUITE_ADD_TEST(suite, test_argrex_basic_003); - SUITE_ADD_TEST(suite, test_argrex_basic_004); - SUITE_ADD_TEST(suite, test_argrex_basic_005); - SUITE_ADD_TEST(suite, test_argrex_basic_006); - SUITE_ADD_TEST(suite, test_argrex_basic_007); - SUITE_ADD_TEST(suite, test_argrex_basic_008); - SUITE_ADD_TEST(suite, test_argrex_basic_009); - SUITE_ADD_TEST(suite, test_argrex_basic_010); - return suite; -} - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif diff --git a/parallel/parallel_src/extern/argtable3-3.2.2/tests/testargstr.c b/parallel/parallel_src/extern/argtable3-3.2.2/tests/testargstr.c deleted file mode 100644 index fccae725..00000000 --- a/parallel/parallel_src/extern/argtable3-3.2.2/tests/testargstr.c +++ /dev/null @@ -1,531 +0,0 @@ -/******************************************************************************* - * This file is part of the argtable3 library. - * - * Copyright (C) 2013-2019 Tom G. Huang - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of STEWART HEITMANN nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ******************************************************************************/ - -#include - -#include "CuTest.h" -#include "argtable3.h" - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable : 4204) -#endif - -void test_argstr_basic_001(CuTest* tc) { - struct arg_str* a = arg_str0(NULL, "hello,world", "STRVAL", "either --hello or --world or none"); - struct arg_str* b = arg_str0("bB", NULL, "STRVAL", "either -b or -B or none"); - struct arg_str* c = arg_str1("cC", NULL, "STRVAL", "either -c or -C"); - struct arg_str* d = arg_strn("dD", "delta", "STRVAL", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, end}; - int nerrors; - - char* argv[] = {"program", "--hello=string1", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 2); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->sval[0], "string1"); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 0); - CuAssertTrue(tc, d->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argstr_basic_002(CuTest* tc) { - struct arg_str* a = arg_str0(NULL, "hello,world", "STRVAL", "either --hello or --world or none"); - struct arg_str* b = arg_str0("bB", NULL, "STRVAL", "either -b or -B or none"); - struct arg_str* c = arg_str1("cC", NULL, "STRVAL", "either -c or -C"); - struct arg_str* d = arg_strn("dD", "delta", "STRVAL", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, end}; - int nerrors; - - char* argv[] = {"program", "-cstring1", "-Dstring2", "-dstring3", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - if (nerrors > 0) - arg_print_errors(stdout, end, argv[0]); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 1); - CuAssertStrEquals(tc, c->sval[0], "string1"); - CuAssertTrue(tc, d->count == 2); - CuAssertStrEquals(tc, d->sval[0], "string2"); - CuAssertStrEquals(tc, d->sval[1], "string3"); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argstr_basic_003(CuTest* tc) { - struct arg_str* a = arg_str0(NULL, "hello,world", "STRVAL", "either --hello or --world or none"); - struct arg_str* b = arg_str0("bB", NULL, "STRVAL", "either -b or -B or none"); - struct arg_str* c = arg_str1("cC", NULL, "STRVAL", "either -c or -C"); - struct arg_str* d = arg_strn("dD", "delta", "STRVAL", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, end}; - int nerrors; - - char* argv[] = {"program", "-Cstring1", "--delta=string2", "--delta=string3", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - if (nerrors > 0) - arg_print_errors(stdout, end, argv[0]); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 1); - CuAssertStrEquals(tc, c->sval[0], "string1"); - CuAssertTrue(tc, d->count == 2); - CuAssertStrEquals(tc, d->sval[0], "string2"); - CuAssertStrEquals(tc, d->sval[1], "string3"); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argstr_basic_004(CuTest* tc) { - struct arg_str* a = arg_str0(NULL, "hello,world", "STRVAL", "either --hello or --world or none"); - struct arg_str* b = arg_str0("bB", NULL, "STRVAL", "either -b or -B or none"); - struct arg_str* c = arg_str1("cC", NULL, "STRVAL", "either -c or -C"); - struct arg_str* d = arg_strn("dD", "delta", "STRVAL", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, end}; - int nerrors; - - char* argv[] = {"program", "--delta=string1", "-cstring2", "-Dstring3", "-bstring4", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - if (nerrors > 0) - arg_print_errors(stdout, end, argv[0]); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 1); - CuAssertStrEquals(tc, b->sval[0], "string4"); - CuAssertTrue(tc, c->count == 1); - CuAssertStrEquals(tc, c->sval[0], "string2"); - CuAssertTrue(tc, d->count == 2); - CuAssertStrEquals(tc, d->sval[0], "string1"); - CuAssertStrEquals(tc, d->sval[1], "string3"); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argstr_basic_005(CuTest* tc) { - struct arg_str* a = arg_str0(NULL, "hello,world", "STRVAL", "either --hello or --world or none"); - struct arg_str* b = arg_str0("bB", NULL, "STRVAL", "either -b or -B or none"); - struct arg_str* c = arg_str1("cC", NULL, "STRVAL", "either -c or -C"); - struct arg_str* d = arg_strn("dD", "delta", "STRVAL", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, end}; - int nerrors; - - char* argv[] = {"program", "-Dstring1", "-Bstring2", "--delta=string3", "-Cstring4", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - if (nerrors > 0) - arg_print_errors(stdout, end, argv[0]); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 1); - CuAssertStrEquals(tc, b->sval[0], "string2"); - CuAssertTrue(tc, c->count == 1); - CuAssertStrEquals(tc, c->sval[0], "string4"); - CuAssertTrue(tc, d->count == 2); - CuAssertStrEquals(tc, d->sval[0], "string1"); - CuAssertStrEquals(tc, d->sval[1], "string3"); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argstr_basic_006(CuTest* tc) { - struct arg_str* a = arg_str0(NULL, "hello,world", "STRVAL", "either --hello or --world or none"); - struct arg_str* b = arg_str0("bB", NULL, "STRVAL", "either -b or -B or none"); - struct arg_str* c = arg_str1("cC", NULL, "STRVAL", "either -c or -C"); - struct arg_str* d = arg_strn("dD", "delta", "STRVAL", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, end}; - int nerrors; - - char* argv[] = {"program", "-Dstring1", "-Bstring2", "--delta=string3", "-Cstring4", "--hello=string5", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - if (nerrors > 0) - arg_print_errors(stdout, end, argv[0]); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->sval[0], "string5"); - CuAssertTrue(tc, b->count == 1); - CuAssertStrEquals(tc, b->sval[0], "string2"); - CuAssertTrue(tc, c->count == 1); - CuAssertStrEquals(tc, c->sval[0], "string4"); - CuAssertTrue(tc, d->count == 2); - CuAssertStrEquals(tc, d->sval[0], "string1"); - CuAssertStrEquals(tc, d->sval[1], "string3"); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argstr_basic_007(CuTest* tc) { - struct arg_str* a = arg_str0(NULL, "hello,world", "STRVAL", "either --hello or --world or none"); - struct arg_str* b = arg_str0("bB", NULL, "STRVAL", "either -b or -B or none"); - struct arg_str* c = arg_str1("cC", NULL, "STRVAL", "either -c or -C"); - struct arg_str* d = arg_strn("dD", "delta", "STRVAL", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, end}; - int nerrors; - - char* argv[] = {"program", "-Dstring1", "-Bstring2", "--delta=string3", "-Cstring4", "--world=string5", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - if (nerrors > 0) - arg_print_errors(stdout, end, argv[0]); - - CuAssertTrue(tc, nerrors == 0); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->sval[0], "string5"); - CuAssertTrue(tc, b->count == 1); - CuAssertStrEquals(tc, b->sval[0], "string2"); - CuAssertTrue(tc, c->count == 1); - CuAssertStrEquals(tc, c->sval[0], "string4"); - CuAssertTrue(tc, d->count == 2); - CuAssertStrEquals(tc, d->sval[0], "string1"); - CuAssertStrEquals(tc, d->sval[1], "string3"); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argstr_basic_008(CuTest* tc) { - struct arg_str* a = arg_str0(NULL, "hello,world", "STRVAL", "either --hello or --world or none"); - struct arg_str* b = arg_str0("bB", NULL, "STRVAL", "either -b or -B or none"); - struct arg_str* c = arg_str1("cC", NULL, "STRVAL", "either -c or -C"); - struct arg_str* d = arg_strn("dD", "delta", "STRVAL", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, end}; - int nerrors; - - char* argv[] = {"program", "-cstring1", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 1); - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 1); - CuAssertStrEquals(tc, c->sval[0], "string1"); - CuAssertTrue(tc, d->count == 0); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argstr_basic_009(CuTest* tc) { - struct arg_str* a = arg_str0(NULL, "hello,world", "STRVAL", "either --hello or --world or none"); - struct arg_str* b = arg_str0("bB", NULL, "STRVAL", "either -b or -B or none"); - struct arg_str* c = arg_str1("cC", NULL, "STRVAL", "either -c or -C"); - struct arg_str* d = arg_strn("dD", "delta", "STRVAL", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, end}; - int nerrors; - - char* argv[] = {"program", "-Dstring1", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 2); - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 0); - CuAssertTrue(tc, d->count == 1); - CuAssertStrEquals(tc, d->sval[0], "string1"); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argstr_basic_010(CuTest* tc) { - struct arg_str* a = arg_str0(NULL, "hello,world", "STRVAL", "either --hello or --world or none"); - struct arg_str* b = arg_str0("bB", NULL, "STRVAL", "either -b or -B or none"); - struct arg_str* c = arg_str1("cC", NULL, "STRVAL", "either -c or -C"); - struct arg_str* d = arg_strn("dD", "delta", "STRVAL", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, end}; - int nerrors; - - char* argv[] = {"program", "-Cstring1", "-Dstring2", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 1); - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 1); - CuAssertStrEquals(tc, c->sval[0], "string1"); - CuAssertTrue(tc, d->count == 1); - CuAssertStrEquals(tc, d->sval[0], "string2"); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argstr_basic_011(CuTest* tc) { - struct arg_str* a = arg_str0(NULL, "hello,world", "STRVAL", "either --hello or --world or none"); - struct arg_str* b = arg_str0("bB", NULL, "STRVAL", "either -b or -B or none"); - struct arg_str* c = arg_str1("cC", NULL, "STRVAL", "either -c or -C"); - struct arg_str* d = arg_strn("dD", "delta", "STRVAL", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, end}; - int nerrors; - - char* argv[] = {"program", "-Dstring1", "-dstring2", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 1); - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 0); - CuAssertTrue(tc, d->count == 2); - CuAssertStrEquals(tc, d->sval[0], "string1"); - CuAssertStrEquals(tc, d->sval[1], "string2"); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argstr_basic_012(CuTest* tc) { - struct arg_str* a = arg_str0(NULL, "hello,world", "STRVAL", "either --hello or --world or none"); - struct arg_str* b = arg_str0("bB", NULL, "STRVAL", "either -b or -B or none"); - struct arg_str* c = arg_str1("cC", NULL, "STRVAL", "either -c or -C"); - struct arg_str* d = arg_strn("dD", "delta", "STRVAL", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, end}; - int nerrors; - - char* argv[] = {"program", "-cs1", "-ds2", "-ds3", "-ds4", "-ds5", "-ds6", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 1); - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 1); - CuAssertStrEquals(tc, c->sval[0], "s1"); - CuAssertTrue(tc, d->count == 4); - CuAssertStrEquals(tc, d->sval[0], "s2"); - CuAssertStrEquals(tc, d->sval[1], "s3"); - CuAssertStrEquals(tc, d->sval[2], "s4"); - CuAssertStrEquals(tc, d->sval[3], "s5"); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argstr_basic_013(CuTest* tc) { - struct arg_str* a = arg_str0(NULL, "hello,world", "STRVAL", "either --hello or --world or none"); - struct arg_str* b = arg_str0("bB", NULL, "STRVAL", "either -b or -B or none"); - struct arg_str* c = arg_str1("cC", NULL, "STRVAL", "either -c or -C"); - struct arg_str* d = arg_strn("dD", "delta", "STRVAL", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, end}; - int nerrors; - - char* argv[] = {"program", "-cs1", "-cs2", "-ds3", "-ds4", "-ds5", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 1); - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 1); - CuAssertStrEquals(tc, c->sval[0], "s1"); - CuAssertTrue(tc, d->count == 3); - CuAssertStrEquals(tc, d->sval[0], "s3"); - CuAssertStrEquals(tc, d->sval[1], "s4"); - CuAssertStrEquals(tc, d->sval[2], "s5"); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argstr_basic_014(CuTest* tc) { - struct arg_str* a = arg_str0(NULL, "hello,world", "STRVAL", "either --hello or --world or none"); - struct arg_str* b = arg_str0("bB", NULL, "STRVAL", "either -b or -B or none"); - struct arg_str* c = arg_str1("cC", NULL, "STRVAL", "either -c or -C"); - struct arg_str* d = arg_strn("dD", "delta", "STRVAL", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, end}; - int nerrors; - - char* argv[] = {"program", "-Cs1", "-ds2", "-Ds3", "--delta=s4", "-bs5", "-Bs6", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 1); - CuAssertTrue(tc, a->count == 0); - CuAssertTrue(tc, b->count == 1); - CuAssertStrEquals(tc, b->sval[0], "s5"); - CuAssertTrue(tc, c->count == 1); - CuAssertStrEquals(tc, c->sval[0], "s1"); - CuAssertTrue(tc, d->count == 3); - CuAssertStrEquals(tc, d->sval[0], "s2"); - CuAssertStrEquals(tc, d->sval[1], "s3"); - CuAssertStrEquals(tc, d->sval[2], "s4"); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argstr_basic_015(CuTest* tc) { - struct arg_str* a = arg_str0(NULL, "hello,world", "STRVAL", "either --hello or --world or none"); - struct arg_str* b = arg_str0("bB", NULL, "STRVAL", "either -b or -B or none"); - struct arg_str* c = arg_str1("cC", NULL, "STRVAL", "either -c or -C"); - struct arg_str* d = arg_strn("dD", "delta", "STRVAL", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, end}; - int nerrors; - - char* argv[] = {"program", "-Cs1", "-ds2", "-Ds3", "--delta=s4", "--hello=s5", "--world=s6", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 1); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->sval[0], "s5"); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 1); - CuAssertStrEquals(tc, c->sval[0], "s1"); - CuAssertTrue(tc, d->count == 3); - CuAssertStrEquals(tc, d->sval[0], "s2"); - CuAssertStrEquals(tc, d->sval[1], "s3"); - CuAssertStrEquals(tc, d->sval[2], "s4"); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -void test_argstr_basic_016(CuTest* tc) { - struct arg_str* a = arg_str0(NULL, "hello,world", "STRVAL", "either --hello or --world or none"); - struct arg_str* b = arg_str0("bB", NULL, "STRVAL", "either -b or -B or none"); - struct arg_str* c = arg_str1("cC", NULL, "STRVAL", "either -c or -C"); - struct arg_str* d = arg_strn("dD", "delta", "STRVAL", 2, 4, "-d|-D|--delta 2..4 occurences"); - struct arg_end* end = arg_end(20); - void* argtable[] = {a, b, c, d, end}; - int nerrors; - - char* argv[] = {"program", "-Cs1", "-ds2", "-Ds3", "--delta=s4", "--hello=s5", "X", NULL}; - int argc = sizeof(argv) / sizeof(char*) - 1; - - CuAssertTrue(tc, arg_nullcheck(argtable) == 0); - - nerrors = arg_parse(argc, argv, argtable); - - CuAssertTrue(tc, nerrors == 1); - CuAssertTrue(tc, a->count == 1); - CuAssertStrEquals(tc, a->sval[0], "s5"); - CuAssertTrue(tc, b->count == 0); - CuAssertTrue(tc, c->count == 1); - CuAssertStrEquals(tc, c->sval[0], "s1"); - CuAssertTrue(tc, d->count == 3); - CuAssertStrEquals(tc, d->sval[0], "s2"); - CuAssertStrEquals(tc, d->sval[1], "s3"); - CuAssertStrEquals(tc, d->sval[2], "s4"); - - arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); -} - -CuSuite* get_argstr_testsuite() { - CuSuite* suite = CuSuiteNew(); - SUITE_ADD_TEST(suite, test_argstr_basic_001); - SUITE_ADD_TEST(suite, test_argstr_basic_002); - SUITE_ADD_TEST(suite, test_argstr_basic_003); - SUITE_ADD_TEST(suite, test_argstr_basic_004); - SUITE_ADD_TEST(suite, test_argstr_basic_005); - SUITE_ADD_TEST(suite, test_argstr_basic_006); - SUITE_ADD_TEST(suite, test_argstr_basic_007); - SUITE_ADD_TEST(suite, test_argstr_basic_008); - SUITE_ADD_TEST(suite, test_argstr_basic_009); - SUITE_ADD_TEST(suite, test_argstr_basic_010); - SUITE_ADD_TEST(suite, test_argstr_basic_011); - SUITE_ADD_TEST(suite, test_argstr_basic_012); - SUITE_ADD_TEST(suite, test_argstr_basic_013); - SUITE_ADD_TEST(suite, test_argstr_basic_014); - SUITE_ADD_TEST(suite, test_argstr_basic_015); - SUITE_ADD_TEST(suite, test_argstr_basic_016); - return suite; -} - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif diff --git a/parallel/parallel_src/interface/parhip_interface.cpp b/parallel/parallel_src/interface/parhip_interface.cpp index b76bb54f..7786c95d 100644 --- a/parallel/parallel_src/interface/parhip_interface.cpp +++ b/parallel/parallel_src/interface/parhip_interface.cpp @@ -1,168 +1,559 @@ +#include "parhip_interface.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include -#include "parhip_interface.h" -#include "parallel_graph_io.h" +#include "communication/mpi_failure.h" +#include "communication/mpi_fixed_reduction.h" +#include "communication/mpi_handles.h" #include "configuration.h" #include "distributed_partitioning/distributed_partitioner.h" -#include "tools/distributed_quality_metrics.h" +#include "parallel_graph_io.h" #include "random_functions.h" +#include "parhip_partition_balance.h" +#include "../../shared/imbalance.h" +#include "../../shared/random_state.h" +#include "tools/distributed_quality_metrics.h" +#include "tools/fatal_diagnostics.h" + +namespace { +using parhip::EdgeID; +using parhip::EdgeWeight; +using parhip::NodeID; +using parhip::NodeWeight; +using parhip::PartitionID; +using parhip::PEID; +using parhip::mpi::communicator_view; +inline constexpr auto pristine_communication_rounds = parhip::ULONG{128}; + +void require_collectively( + bool local_condition, + communicator_view communicator, + std::string_view diagnostic, + std::string_view collective_context = + "MPI_Allreduce(ParHIP input validation)") noexcept { + auto const local = local_condition ? 1 : 0; + auto global = 0; + parhip::mpi::check_or_abort( + MPI_Allreduce(&local, &global, 1, MPI_INT, MPI_MIN, + communicator.native_handle()), + communicator.native_handle(), collective_context); + if (global == 0) { + parhip::mpi::abort_on_programming_error(communicator.native_handle(), + diagnostic); + } +} + +void require_capacity_collectively(bool local_condition, + communicator_view communicator, + std::string_view diagnostic) noexcept { + auto const local = local_condition ? 1 : 0; + auto global = 0; + parhip::mpi::check_or_abort( + MPI_Allreduce(&local, &global, 1, MPI_INT, MPI_MIN, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(ParHIP capacity validation)"); + if (global == 0) { + parhip::mpi::abort_on_capacity_failure(communicator.native_handle(), + "ParHIPPartitionKWay", diagnostic); + } +} + +template +[[nodiscard]] auto agree_integral(T local_value, + communicator_view communicator, + std::string_view diagnostic) noexcept -> T { + static_assert(parhip::mpi::mpi_integral_reduction_datatype); + auto minimum = T{}; + auto maximum = T{}; + auto const local = std::array{local_value}; + auto minimum_span = std::span{&minimum, 1}; + auto maximum_span = std::span{&maximum, 1}; + parhip::mpi::all_reduce_bounded(std::span{local}, minimum_span, + parhip::mpi::reduction_kind::minimum, + communicator, + "MPI_Allreduce(ParHIP common input minimum)"); + parhip::mpi::all_reduce_bounded(std::span{local}, maximum_span, + parhip::mpi::reduction_kind::maximum, + communicator, + "MPI_Allreduce(ParHIP common input maximum)"); + if (minimum != maximum) { + parhip::mpi::abort_on_programming_error(communicator.native_handle(), + diagnostic); + } + return minimum; +} + +[[nodiscard]] auto agree_double(double local_value, + communicator_view communicator, + std::string_view diagnostic) noexcept + -> double { + auto const canonical = local_value == 0.0 ? 0.0 : local_value; + auto const bits = std::bit_cast(canonical); + static_cast(agree_integral(bits, communicator, diagnostic)); + return canonical; +} + +[[nodiscard]] auto validated_distribution(idxtype const* distribution, + PEID size, + communicator_view communicator) + -> std::vector { + require_capacity_collectively( + size < std::numeric_limits::max(), communicator, + "communicator size cannot be represented as a distribution extent"); + auto local = std::vector(distribution, distribution + size + 1); + auto minimum = std::vector(local.size()); + auto maximum = std::vector(local.size()); + parhip::mpi::all_reduce_bounded( + std::span{local}, std::span{minimum}, + parhip::mpi::reduction_kind::minimum, communicator, + "MPI_Allreduce(ParHIP vertex distribution minimum)"); + parhip::mpi::all_reduce_bounded( + std::span{local}, std::span{maximum}, + parhip::mpi::reduction_kind::maximum, communicator, + "MPI_Allreduce(ParHIP vertex distribution maximum)"); + require_collectively( + minimum == maximum, communicator, + "ParHIP vertex distribution differs across communicator"); + require_collectively( + local.front() == 0 && std::ranges::is_sorted(local), communicator, + "ParHIP vertex distribution must start at zero and be monotone"); + return local; +} + +[[nodiscard]] auto checked_collective_sum(NodeWeight local_value, + communicator_view communicator, + std::string_view diagnostic) + -> NodeWeight { + auto const local = std::array{local_value}; + auto global = std::array{}; + parhip::mpi::all_reduce_checked_sum( + std::span{local}, std::span{global}, + communicator, "MPI_Allreduce(ParHIP checked scalar sum)", + "ParHIPPartitionKWay", diagnostic); + return global.front(); +} + +class null_streambuf final : public std::streambuf { + protected: + auto overflow(traits_type::int_type character) + -> traits_type::int_type override { + return traits_type::not_eof(character); + } +}; + +class scoped_output_suppression final { + public: + explicit scoped_output_suppression(bool suppress) + : backup_(std::cout.rdbuf()) { + if (suppress) { + std::cout.rdbuf(&sink_); + } + } + + ~scoped_output_suppression() { std::cout.rdbuf(backup_); } + + scoped_output_suppression(scoped_output_suppression const&) = delete; + auto operator=(scoped_output_suppression const&) + -> scoped_output_suppression& = delete; + + private: + std::streambuf* backup_; + null_streambuf sink_; +}; + +[[nodiscard]] auto exact_upper_bound(NodeWeight global_weight, + PartitionID block_count, + unsigned imbalance_percent, + communicator_view communicator) + -> NodeWeight { + auto const result = kahip::random_compat::exact_partition_upper_bound( + global_weight, static_cast(block_count), imbalance_percent); + require_capacity_collectively( + result.has_value(), communicator, + "partition upper bound exceeds the graph-weight domain"); + return *result; +} + +void require_valid_partition(parhip::parallel_graph_access& graph, + std::span vertex_weights, + parhip::PPartitionConfig const& config, + NodeWeight global_node_weight, + double raw_imbalance, + kahip::balance::normalized_imbalance imbalance, + PEID operation_rank, + communicator_view communicator) { + auto local_block_weights = + std::vector(static_cast(config.k), 0); + auto local_valid = true; + for (NodeID node = 0; node < graph.number_of_local_nodes(); ++node) { + auto const block = graph.getNodeLabel(node); + if (block >= config.k) { + local_valid = false; + continue; + } + auto& block_weight = local_block_weights[static_cast(block)]; + auto const node_weight = vertex_weights[static_cast(node)]; + if (node_weight > std::numeric_limits::max() - block_weight) { + parhip::mpi::abort_on_capacity_failure( + communicator.native_handle(), "ParHIPPartitionKWay", + "local partition block weight exceeds the graph-weight domain"); + } + block_weight += node_weight; + } + require_collectively( + local_valid, communicator, + "ParHIP produced a partition label outside the block domain"); + + auto global_block_weights = + std::vector(local_block_weights.size()); + parhip::mpi::all_reduce_checked_sum( + std::span{local_block_weights}, + std::span{global_block_weights}, communicator, + "MPI_Allreduce(ParHIP partition block weights)", "ParHIPPartitionKWay", + "global partition block-weight sum exceeds the graph-weight domain"); + auto const [heaviest_block, heaviest_weight] = + parhip::detail::lowest_id_heaviest_block( + std::span{global_block_weights}); + auto const local_balanced = heaviest_weight <= config.upper_bound_partition; + auto globally_balanced = 0; + auto const local = local_balanced ? 1 : 0; + parhip::mpi::check_or_abort( + MPI_Allreduce(&local, &globally_balanced, 1, MPI_INT, MPI_MIN, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(ParHIP partition balance validation)"); + if (globally_balanced == 0) { + if (operation_rank == 0) { + kahip::diagnostics::critical( + "ParHIP partition balance failure: raw imbalance=", + std::setprecision(std::numeric_limits::max_digits10), + raw_imbalance, ", effective percentage=", imbalance.effective_percent, + "%, normalization status=", + imbalance.was_normalized ? "true" : "false", + ", total weight=", global_node_weight, ", block count=", config.k, + ", configured bound=", config.upper_bound_partition, + ", lowest-ID heaviest block=", heaviest_block, + ", actual weight=", heaviest_weight, + ", excess=", heaviest_weight - config.upper_bound_partition); + } + parhip::mpi::check_or_abort( + MPI_Barrier(communicator.native_handle()), + communicator.native_handle(), + "MPI_Barrier(ParHIP partition balance diagnostic ordering)"); + MPI_Abort(communicator.native_handle(), EXIT_FAILURE); + std::abort(); + } +} + +// 3% imbalance is specified as imbalance = 0.03. +void parhip_partition_kway(idxtype const* vtxdist, + idxtype const* xadj, + idxtype const* adjncy, + idxtype const* vwgt, + idxtype const* adjwgt, + int const* nparts, + double const* imbalance, + bool suppress_output, + int seed, + int mode, + int* edgecut, + idxtype* part, + communicator_view communicator) { + using namespace parhip; + + auto const rank = communicator.rank(); + auto const size = communicator.size(); + + require_collectively( + vtxdist != nullptr && xadj != nullptr && nparts != nullptr && + imbalance != nullptr && edgecut != nullptr, + communicator, "ParHIP required input pointers are invalid"); + + auto const block_count = agree_integral( + *nparts, communicator, "ParHIP block count differs across communicator"); + require_collectively(block_count > 0, communicator, + "ParHIP block count must be greater than zero"); + require_collectively(std::isfinite(*imbalance) && *imbalance >= 0.0, + communicator, + "ParHIP imbalance must be finite and nonnegative"); + auto const common_imbalance = agree_double( + *imbalance, communicator, "ParHIP imbalance differs across communicator"); + auto const normalized_imbalance = + kahip::balance::normalize_fractional_imbalance(common_imbalance); + require_capacity_collectively( + normalized_imbalance.has_value(), communicator, + "imbalance percentage exceeds the unsigned int domain"); + static_cast(agree_integral(seed, communicator, + "ParHIP seed differs across communicator")); + auto const common_mode = agree_integral( + mode, communicator, "ParHIP mode differs across communicator"); + constexpr auto supported_modes = std::array{ + ULTRAFASTMESH, FASTMESH, ECOMESH, ULTRAFASTSOCIAL, FASTSOCIAL, ECOSOCIAL}; + require_collectively( + std::ranges::find(supported_modes, common_mode) != supported_modes.end(), + communicator, "ParHIP mode is outside the supported domain"); + static_cast( + agree_integral(suppress_output ? 1 : 0, communicator, + "ParHIP output suppression differs across communicator")); + static_cast(agree_integral( + vwgt == nullptr ? 0 : 1, communicator, + "ParHIP optional vertex-weight presence differs across communicator")); + static_cast(agree_integral( + adjwgt == nullptr ? 0 : 1, communicator, + "ParHIP optional edge-weight presence differs across communicator")); + + auto vertex_dist = validated_distribution(vtxdist, size, communicator); + auto const first = vertex_dist[static_cast(rank)]; + auto const next = vertex_dist[static_cast(rank) + 1]; + auto const local_number_of_nodes = next - first; + require_capacity_collectively( + std::in_range(local_number_of_nodes), communicator, + "local vertex count exceeds addressable storage"); + auto const local_node_count = static_cast(local_number_of_nodes); + + auto const offsets_valid = + xadj[0] == 0 && std::ranges::is_sorted( + std::span{xadj, local_node_count + 1}); + require_collectively(offsets_valid, communicator, + "ParHIP local CSR offsets are invalid"); + auto const local_number_of_edges = xadj[local_node_count]; + require_capacity_collectively( + std::in_range(local_number_of_edges), communicator, + "local edge count exceeds addressable storage"); + auto const local_edge_count = static_cast(local_number_of_edges); + require_collectively( + local_edge_count == 0 || adjncy != nullptr, communicator, + "ParHIP adjacency pointer is missing for nonempty edge storage"); + require_collectively( + local_node_count == 0 || part != nullptr, communicator, + "ParHIP partition output pointer is missing for nonempty local storage"); + + auto const global_number_of_nodes = vertex_dist.back(); + auto const neighbors = local_edge_count == 0 ? std::span{} + : std::span{ + adjncy, local_edge_count}; + require_collectively( + std::ranges::all_of( + neighbors, + [&](idxtype neighbor) { + auto const is_nonnegative = [](T value) { + if constexpr (std::is_signed_v) { + return value >= 0; + } + return true; + }(neighbor); + return is_nonnegative && neighbor < global_number_of_nodes; + }), + communicator, + "ParHIP adjacency contains a vertex outside the global domain"); + + auto vertex_weights = std::vector(local_node_count, 1); + auto local_overall_node_weight = NodeWeight{local_number_of_nodes}; + if (vwgt != nullptr) { + local_overall_node_weight = 0; + auto local_weight_valid = true; + for (auto index = std::size_t{0}; index < local_node_count; ++index) { + vertex_weights[index] = vwgt[index]; + if (vwgt[index] > + std::numeric_limits::max() - local_overall_node_weight) { + local_weight_valid = false; + } else { + local_overall_node_weight += vwgt[index]; + } + } + require_capacity_collectively( + local_weight_valid, communicator, + "local vertex-weight sum exceeds the graph-weight domain"); + } + auto const global_node_weight = checked_collective_sum( + local_overall_node_weight, communicator, + "global vertex-weight sum exceeds the graph-weight domain"); + auto const global_number_of_edges = + checked_collective_sum(local_number_of_edges, communicator, + "global edge count exceeds the graph-size domain"); + + auto suppression = scoped_output_suppression{suppress_output}; + // start_construction consumes this process-global tuning value before the + // per-configuration value is installed below. Restore the pristine upstream + // entry state so a previous C API call cannot change this call's semantics. + parallel_graph_access::set_comm_rounds(pristine_communication_rounds); + parallel_graph_access graph{communicator.native_handle()}; + graph.start_construction(local_number_of_nodes, local_number_of_edges, + global_number_of_nodes, global_number_of_edges); + graph.set_range(first, local_number_of_nodes == 0 ? first : next - 1); + graph.set_range_array(vertex_dist); + for (NodeID local_node = 0; local_node < local_number_of_nodes; + ++local_node) { + auto const node = graph.new_node(); + graph.setNodeWeight(node, + vertex_weights[static_cast(local_node)]); + graph.setNodeLabel(node, first + node); + graph.setSecondPartitionIndex(node, 0); + for (EdgeID edge_index = xadj[local_node]; + edge_index < xadj[local_node + 1]; ++edge_index) { + auto const edge = graph.new_edge(node, adjncy[edge_index]); + graph.setEdgeWeight(edge, adjwgt == nullptr ? 1 : adjwgt[edge_index]); + } + } + graph.finish_construction(); + + PPartitionConfig partition_config; + configuration config; + config.standard(partition_config); + switch (common_mode) { + case FASTMESH: + config.fast(partition_config); + partition_config.cluster_coarsening_factor = 20000; + break; + case ULTRAFASTMESH: + config.ultrafast(partition_config); + partition_config.cluster_coarsening_factor = 20000; + break; + case ECOMESH: + config.eco(partition_config, communicator); + partition_config.cluster_coarsening_factor = 20000; + break; + case FASTSOCIAL: + config.fast(partition_config); + break; + case ECOSOCIAL: + config.eco(partition_config, communicator); + break; + case ULTRAFASTSOCIAL: + config.ultrafast(partition_config); + break; + default: + parhip::mpi::abort_on_programming_error( + communicator.native_handle(), + "ParHIP mode escaped collective domain validation"); + } + + partition_config.k = static_cast(block_count); + partition_config.seed = seed; + partition_config.stop_factor /= block_count; + auto const derived_seed = + kahip::random_compat::outer_rank_seed(seed, size, rank); + require_collectively( + derived_seed.has_value(), communicator, + "rank-derived random seed uses an invalid process count or rank"); + partition_config.seed = *derived_seed; + + std::srand(partition_config.seed); + random_functions::setSeed(partition_config.seed); + parallel_graph_access::set_comm_rounds(partition_config.comm_rounds / size); + parallel_graph_access::set_comm_rounds_up(partition_config.comm_rounds / + size); + distributed_partitioner::generate_random_choices(partition_config, + communicator); + + partition_config.inbalance = normalized_imbalance->effective_percent; + partition_config.number_of_overall_nodes = graph.number_of_global_nodes(); + partition_config.upper_bound_partition = + exact_upper_bound(global_node_weight, partition_config.k, + partition_config.inbalance, communicator); + + timer runtime; + distributed_partitioner partitioner; + partitioner.perform_partitioning(communicator.native_handle(), + partition_config, graph); + parhip::mpi::check_or_abort(MPI_Barrier(communicator.native_handle()), + communicator.native_handle(), + "MPI_Barrier(ParHIP partition completion)"); + auto const running_time = runtime.elapsed(); + + require_valid_partition(graph, vertex_weights, partition_config, + global_node_weight, common_imbalance, + *normalized_imbalance, rank, + communicator); + distributed_quality_metrics metrics; + auto const global_edge_cut = + metrics.edge_cut(graph, communicator.native_handle()); + require_capacity_collectively( + std::in_range(global_edge_cut), communicator, + "global edge cut exceeds the C interface int domain"); + + *edgecut = static_cast(global_edge_cut); + for (NodeID local_node = 0; local_node < local_number_of_nodes; + ++local_node) { + part[local_node] = graph.getNodeLabel(local_node); + } + + if (!suppress_output) { + auto const balance = + metrics.balance(partition_config, graph, communicator.native_handle()); + if (rank == 0) { + std::cout << "log>=====================================\n" + << "log>============AND WE R DONE============\n" + << "log>=====================================\n" + << "log>total partitioning time elapsed " << running_time + << '\n' + << "log>final edge cut " << *edgecut << '\n' + << "log>final balance " << balance << std::endl; + } + } +} +} // namespace +extern "C" void ParHIPPartitionKWay(idxtype* vtxdist, + idxtype* xadj, + idxtype* adjncy, + idxtype* vwgt, + idxtype* adjwgt, + int* nparts, + double* imbalance, + bool suppress_output, + int seed, + int mode, + int* edgecut, + idxtype* part, + MPI_Comm* comm) noexcept { + using parhip::mpi::abort_on_exception; + using parhip::mpi::abort_on_programming_error; + using parhip::mpi::communicator; + using parhip::mpi::communicator_view; + using parhip::mpi::run_with_exception_barrier; -// 3% imbalance should be specified as imbalance = 0.03 -void ParHIPPartitionKWay(idxtype *vtxdist, idxtype *xadj, idxtype *adjncy, idxtype *vwgt, idxtype *adjwgt, - int *nparts, double* imbalance, bool suppress_output, int seed, int mode, int *edgecut, idxtype *part, - MPI_Comm *comm) { - - - - std::streambuf* backup = std::cout.rdbuf(); - std::ofstream ofs; - ofs.open("/dev/null"); - - if(suppress_output) { - std::cout.rdbuf(ofs.rdbuf()); - } - - PEID rank, size; - MPI_Comm_rank( *comm, &rank); - MPI_Comm_size( *comm, &size); - - //building internal graph data structure - idxtype local_number_of_nodes = vtxdist[rank+1] - vtxdist[rank]; - idxtype local_number_of_edges = xadj[local_number_of_nodes]; - idxtype number_of_nodes = vtxdist[size]; - - std::vector< NodeID > vertex_weights(local_number_of_nodes,1); - NodeWeight local_overall_node_weight = local_number_of_nodes; - NodeWeight global_node_weight = number_of_nodes; - if( vwgt != NULL ) { - local_overall_node_weight = 0; - global_node_weight = 0; - for( unsigned long long i = 0; i < local_number_of_nodes; i++) { - vertex_weights[i] = vwgt[i]; - local_overall_node_weight += vwgt[i]; - } - MPI_Allreduce(&local_overall_node_weight, &global_node_weight, 1, MPI_UNSIGNED_LONG_LONG, MPI_SUM, *comm); - } - - //// pe p obtains nodes p*ceil(n/size) to (p+1)floor(n/size) and the edges - idxtype from = vtxdist[rank]; - idxtype to = vtxdist[rank+1]-1; - - - unsigned long long global_number_of_edges = 0; - MPI_Allreduce(&local_number_of_edges, &global_number_of_edges, 1, MPI_UNSIGNED_LONG_LONG, MPI_SUM, *comm); - - parallel_graph_access G(*comm); - G.start_construction(local_number_of_nodes, local_number_of_edges, number_of_nodes, global_number_of_edges); - G.set_range(from, to); - std::vector< NodeID > vertex_dist( size+1, 0 ); - for( PEID peID = 0; peID <= size; peID++) { - vertex_dist[peID] = vtxdist[peID]; - } - G.set_range_array(vertex_dist); - - if( adjwgt != NULL ) { - for (NodeID i = 0; i < local_number_of_nodes; ++i) { - NodeID node = G.new_node(); - G.setNodeWeight(node, vertex_weights[i]); - G.setNodeLabel(node, from+node); - G.setSecondPartitionIndex(node, 0); - - for (ULONG j = xadj[i]; j < xadj[i + 1]; j++) { - EdgeID e = G.new_edge(node, adjncy[j]); - G.setEdgeWeight(e, adjwgt[j]); - } - } - } else { - for (NodeID i = 0; i < local_number_of_nodes; ++i) { - NodeID node = G.new_node(); - G.setNodeWeight(node, vertex_weights[i]); - G.setNodeLabel(node, from+node); - G.setSecondPartitionIndex(node, 0); - - for (ULONG j = xadj[i]; j < xadj[i + 1]; j++) { - EdgeID e = G.new_edge(node, adjncy[j]); - G.setEdgeWeight(e, 1); - } - } - } - - G.finish_construction(); - - PPartitionConfig partition_config; - configuration cfg; - cfg.standard(partition_config); - - switch( mode ) { - case FASTMESH: - cfg.fast(partition_config); - partition_config.cluster_coarsening_factor = 20000; - break; - case ULTRAFASTMESH: - cfg.ultrafast(partition_config); - partition_config.cluster_coarsening_factor = 20000; - break; - case ECOMESH: - cfg.eco(partition_config); - partition_config.cluster_coarsening_factor = 20000; - break; - case FASTSOCIAL: - cfg.fast(partition_config); - break; - case ECOSOCIAL: - cfg.eco(partition_config); - break; - case ULTRAFASTSOCIAL: - cfg.ultrafast(partition_config); - break; - default: - cfg.fast(partition_config); - break; - } - - partition_config.k = *nparts; - partition_config.seed = seed; - partition_config.stop_factor /= partition_config.k; - if(rank != 0) partition_config.seed = partition_config.seed*size+rank; - - srand(partition_config.seed); - - random_functions::setSeed(partition_config.seed); - parallel_graph_access::set_comm_rounds( partition_config.comm_rounds/size ); - parallel_graph_access::set_comm_rounds_up( partition_config.comm_rounds/size); - distributed_partitioner::generate_random_choices( partition_config ); - - timer t; - partition_config.inbalance = 100*(*imbalance); - double epsilon = (partition_config.inbalance)/100.0; - partition_config.number_of_overall_nodes = G.number_of_global_nodes(); - partition_config.upper_bound_partition = (1+epsilon)*ceil(global_node_weight/(double)partition_config.k); - - distributed_partitioner dpart; - dpart.perform_partitioning( *comm, partition_config, G); - MPI_Barrier(*comm); - double running_time = t.elapsed(); - - ofs.close(); - std::cout.rdbuf(backup); - - distributed_quality_metrics qm; - *edgecut = qm.edge_cut( G, *comm ); - - if (!suppress_output) { - double balance = qm.balance( partition_config, G, *comm ); - if (rank == 0) { - std::cout << "log>" << "=====================================" << std::endl; - std::cout << "log>" << "============AND WE R DONE============" << std::endl; - std::cout << "log>" << "=====================================" << std::endl; - std::cout << "log>total partitioning time elapsed " << running_time << std::endl; - std::cout << "log>final edge cut " << *edgecut << std::endl; - std::cout << "log>final balance " << balance << std::endl; - } - } - - for (NodeID i = 0; i < local_number_of_nodes; ++i) { - part[i] = G.getNodeLabel(i); - } + if (comm == nullptr) { + abort_on_programming_error(MPI_COMM_WORLD, + "ParHIP communicator pointer is null"); + } + auto const caller = *comm; + run_with_exception_barrier( + [&] { + auto owned = communicator{communicator_view{caller}}; + auto const affected = owned.view(); + run_with_exception_barrier( + [&] { + parhip_partition_kway(vtxdist, xadj, adjncy, vwgt, adjwgt, nparts, + imbalance, suppress_output, seed, mode, + edgecut, part, affected); + }, + [affected](std::exception_ptr failure) noexcept { + abort_on_exception(affected.native_handle(), + "ParHIPPartitionKWay", failure); + }); + }, + [caller](std::exception_ptr failure) noexcept { + abort_on_exception( + caller, "ParHIPPartitionKWay communicator acquisition", failure); + }); } diff --git a/parallel/parallel_src/interface/parhip_interface.h b/parallel/parallel_src/interface/parhip_interface.h index fa814733..091298e4 100644 --- a/parallel/parallel_src/interface/parhip_interface.h +++ b/parallel/parallel_src/interface/parhip_interface.h @@ -7,26 +7,65 @@ #ifndef PARHIP_INTERFACE #define PARHIP_INTERFACE #include -#ifdef __cplusplus -extern "C" -{ +#ifndef __cplusplus +#include +#endif + +#ifdef __cplusplus +#define PARHIP_NOEXCEPT noexcept +#else +#define PARHIP_NOEXCEPT #endif typedef unsigned long long idxtype; -const int ULTRAFASTMESH = 0; -const int FASTMESH = 1; -const int ECOMESH = 2; -const int ULTRAFASTSOCIAL = 3; -const int FASTSOCIAL = 4; -const int ECOSOCIAL = 5; +#ifdef __cplusplus +inline constexpr int ULTRAFASTMESH = 0; +inline constexpr int FASTMESH = 1; +inline constexpr int ECOMESH = 2; +inline constexpr int ULTRAFASTSOCIAL = 3; +inline constexpr int PARHIP_FASTSOCIAL = 4; +inline constexpr int PARHIP_ECOSOCIAL = 5; +#else +enum { + ULTRAFASTMESH = 0, + FASTMESH = 1, + ECOMESH = 2, + ULTRAFASTSOCIAL = 3, + PARHIP_FASTSOCIAL = 4, + PARHIP_ECOSOCIAL = 5 +}; +#endif + +/* See kaHIP_interface.h for the legacy-name compatibility contract. */ +#ifndef KAHIP_LEGACY_SOCIAL_MODE_NAMES_DEFINED +#define KAHIP_LEGACY_SOCIAL_MODE_NAMES_DEFINED +#ifdef __cplusplus +inline constexpr int FASTSOCIAL = PARHIP_FASTSOCIAL; +inline constexpr int ECOSOCIAL = PARHIP_ECOSOCIAL; +#else +enum { FASTSOCIAL = PARHIP_FASTSOCIAL, ECOSOCIAL = PARHIP_ECOSOCIAL }; +#endif +#endif + +/* + * Calls are sequential and non-reentrant within a process. ParHIP preserves + * the deterministic upstream PRNG and refinement caches, whose state is + * process/thread global. Repeated sequential calls are supported; concurrent + * calls from multiple threads are not. + */ +#ifdef __cplusplus +extern "C" { +#endif void ParHIPPartitionKWay(idxtype *vtxdist, idxtype *xadj, idxtype *adjncy, idxtype *vwgt, idxtype *adjwgt, int *nparts, double* imbalance, bool suppress_output, int seed, int mode, int *edgecut, idxtype *part, - MPI_Comm *comm); + MPI_Comm *comm) PARHIP_NOEXCEPT; #ifdef __cplusplus } #endif +#undef PARHIP_NOEXCEPT + #endif /* end of include guard: PARHIP_INTERFAVE */ diff --git a/parallel/parallel_src/interface/parhip_partition_balance.h b/parallel/parallel_src/interface/parhip_partition_balance.h new file mode 100644 index 00000000..6507c4bd --- /dev/null +++ b/parallel/parallel_src/interface/parhip_partition_balance.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + +namespace parhip::detail { +template +[[nodiscard]] constexpr auto lowest_id_heaviest_block( + std::span block_weights) noexcept + -> std::pair { + auto heaviest_block = std::size_t{0}; + auto heaviest_weight = block_weights.front(); + for (auto block = std::size_t{1}; block < block_weights.size(); ++block) { + if (block_weights[block] > heaviest_weight) { + heaviest_block = block; + heaviest_weight = block_weights[block]; + } + } + return {heaviest_block, heaviest_weight}; +} +} // namespace parhip::detail diff --git a/parallel/parallel_src/lib/communication/contiguous_owner_layout.h b/parallel/parallel_src/lib/communication/contiguous_owner_layout.h new file mode 100644 index 00000000..5ada88dc --- /dev/null +++ b/parallel/parallel_src/lib/communication/contiguous_owner_layout.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace parhip::mpi { +template +class contiguous_owner_layout { +public: + constexpr contiguous_owner_layout(Id total, std::size_t partitions) + : total_(total), partitions_(partitions) { + if (partitions_ == 0 || + partitions_ > static_cast( + std::numeric_limits::max())) { + throw std::invalid_argument{ + "contiguous ownership requires a representable partition count"}; + } + + auto const divisor = static_cast(partitions_); + chunk_size_ = total_ == 0 + ? Id{1} + : total_ / divisor + Id{total_ % divisor != 0}; + } + + [[nodiscard]] constexpr auto total() const noexcept -> Id { + return total_; + } + + [[nodiscard]] constexpr auto partition_count() const noexcept + -> std::size_t { + return partitions_; + } + + [[nodiscard]] constexpr auto chunk_size() const noexcept -> Id { + return chunk_size_; + } + + [[nodiscard]] constexpr auto owner(Id id) const noexcept + -> std::optional { + if (id >= total_) { + return std::nullopt; + } + auto const result = static_cast(id / chunk_size_); + return result < partitions_ ? std::optional{result} : std::nullopt; + } + + [[nodiscard]] constexpr auto boundary(std::size_t partition) const noexcept + -> Id { + if (partition == 0 || total_ == 0) { + return Id{0}; + } + if (partition >= partitions_) { + return total_; + } + + auto const position = static_cast(partition); + if (chunk_size_ > total_ / position) { + return total_; + } + return std::min(total_, static_cast(chunk_size_ * position)); + } + + [[nodiscard]] constexpr auto begin(std::size_t partition) const noexcept + -> Id { + return boundary(partition); + } + + [[nodiscard]] constexpr auto end(std::size_t partition) const noexcept + -> Id { + return partition >= partitions_ ? total_ : boundary(partition + 1); + } + +private: + Id total_ = 0; + std::size_t partitions_ = 0; + Id chunk_size_ = 1; +}; +} // namespace parhip::mpi diff --git a/parallel/parallel_src/lib/communication/dummy_operations.cpp b/parallel/parallel_src/lib/communication/dummy_operations.cpp index 27e5ab9e..857e4d38 100644 --- a/parallel/parallel_src/lib/communication/dummy_operations.cpp +++ b/parallel/parallel_src/lib/communication/dummy_operations.cpp @@ -6,9 +6,14 @@ *****************************************************************************/ #include + +#include +#include #include -#include "dummy_operations.h" +#include "communication/mpi_failure.h" +#include "dummy_operations.h" +namespace parhip { dummy_operations::dummy_operations() { } @@ -17,58 +22,65 @@ dummy_operations::~dummy_operations() { } -void dummy_operations::run_collective_dummy_operations() { - int rank, size; - MPI_Comm_rank( MPI_COMM_WORLD, &rank); - MPI_Comm_size( MPI_COMM_WORLD, &size); - - // Run Broadcast - { - int x; - MPI_Comm_rank( MPI_COMM_WORLD, &x); - MPI_Bcast(&x, 1, MPI_INT, 0, MPI_COMM_WORLD); - } - // Run Allgather. - { - int x, size; - MPI_Comm_rank( MPI_COMM_WORLD, &x); - MPI_Comm_size( MPI_COMM_WORLD, &size); - - std::vector rcv(size); - MPI_Allgather(&x, 1, MPI_INT, &rcv[0], 1, MPI_INT, MPI_COMM_WORLD); - } +void dummy_operations::run_collective_dummy_operations( + mpi::communicator_view communicator) { + auto const rank = communicator.rank(); + auto const size = communicator.size(); + auto const native_communicator = communicator.native_handle(); - // Run Allreduce. - { - int x; - MPI_Comm_rank( MPI_COMM_WORLD, &x); - - int y = 0; - MPI_Allreduce(&x, &y, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD); - } + // Run Broadcast + { + auto x = rank; + mpi::check_or_abort(MPI_Bcast(&x, 1, MPI_INT, 0, native_communicator), + native_communicator, + "MPI_Bcast(application warm-up)"); + } + // Run Allgather. + { + auto received = std::vector(static_cast(size)); + mpi::check_or_abort( + MPI_Allgather(&rank, 1, MPI_INT, received.data(), 1, MPI_INT, + native_communicator), + native_communicator, "MPI_Allgather(application warm-up)"); + } - // Dummy Prefix Sum - { - int x = 1; - int y = 0; + // Run Allreduce. + { + int y = 0; + mpi::check_or_abort( + MPI_Allreduce(&rank, &y, 1, MPI_INT, MPI_SUM, native_communicator), + native_communicator, "MPI_Allreduce(application warm-up)"); + } - MPI_Scan(&x, &y, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD); - } + // Dummy Prefix Sum + { + int x = 1; + int y = 0; - // Run Alltoallv. - { - std::vector snd(size); - std::vector rcv(size); - std::vector scounts(size, 1); - std::vector rcounts(size, 1); - std::vector sdispls(size); - std::vector rdispls(size); - for (int i = 0, iend = sdispls.size(); i < iend; ++i) { - sdispls[i] = rdispls[i] = i; - } - MPI_Alltoallv(&snd[0], &scounts[0], &sdispls[0], MPI_INT, - &rcv[0], &rcounts[0], &rdispls[0], MPI_INT, MPI_COMM_WORLD); - } + mpi::check_or_abort( + MPI_Scan(&x, &y, 1, MPI_INT, MPI_SUM, native_communicator), + native_communicator, "MPI_Scan(application warm-up)"); + } + + // Run Alltoallv. + { + auto const process_count = static_cast(size); + auto sent = std::vector(process_count); + auto received = std::vector(process_count); + auto send_counts = std::vector(process_count, 1); + auto receive_counts = std::vector(process_count, 1); + auto send_displacements = std::vector(process_count); + auto receive_displacements = std::vector(process_count); + std::iota(send_displacements.begin(), send_displacements.end(), 0); + std::ranges::copy(send_displacements, receive_displacements.begin()); + mpi::check_or_abort( + MPI_Alltoallv(sent.data(), send_counts.data(), send_displacements.data(), + MPI_INT, received.data(), receive_counts.data(), + receive_displacements.data(), MPI_INT, + native_communicator), + native_communicator, "MPI_Alltoallv(application warm-up)"); + } } +} diff --git a/parallel/parallel_src/lib/communication/dummy_operations.h b/parallel/parallel_src/lib/communication/dummy_operations.h index 6bf25251..ef8d3c3d 100644 --- a/parallel/parallel_src/lib/communication/dummy_operations.h +++ b/parallel/parallel_src/lib/communication/dummy_operations.h @@ -9,13 +9,16 @@ #ifndef DUMMY_OPERATIONS_UVZ6V6T7 #define DUMMY_OPERATIONS_UVZ6V6T7 +#include "communication/mpi_handles.h" + +namespace parhip { class dummy_operations { public: - dummy_operations(); - virtual ~dummy_operations(); + dummy_operations(); + virtual ~dummy_operations(); - void run_collective_dummy_operations(); + void run_collective_dummy_operations(mpi::communicator_view communicator); }; - +} #endif /* end of include guard: DUMMY_OPERATIONS_UVZ6V6T7 */ diff --git a/parallel/parallel_src/lib/communication/ghost_exchange_plan.cpp b/parallel/parallel_src/lib/communication/ghost_exchange_plan.cpp new file mode 100644 index 00000000..b95bc230 --- /dev/null +++ b/parallel/parallel_src/lib/communication/ghost_exchange_plan.cpp @@ -0,0 +1,300 @@ +#include "communication/ghost_exchange_plan.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_failure.h" +#include "data_structure/parallel_graph_access.h" + +namespace parhip { +namespace { +struct flat_segments { + std::vector storage; + std::vector offsets; + std::vector counts; +}; + +[[nodiscard]] auto flatten(std::vector> const& segments) + -> flat_segments { + auto result = flat_segments{}; + result.offsets.reserve(segments.size()); + result.counts.reserve(segments.size()); + for (auto const& segment : segments) { + result.offsets.push_back(result.storage.size()); + result.counts.push_back(segment.size()); + result.storage.insert(result.storage.end(), segment.begin(), segment.end()); + } + return result; +} + +[[nodiscard]] auto segment(std::span storage, + std::span offsets, + std::span counts, + std::size_t index) noexcept + -> std::span { + if (index >= offsets.size() || index >= counts.size() || + offsets[index] > storage.size() || + counts[index] > storage.size() - offsets[index]) { + return {}; + } + return storage.subspan(offsets[index], counts[index]); +} +} // namespace + +ghost_exchange_plan::ghost_exchange_plan( + mpi::distributed_graph topology, + std::vector outgoing_nodes, + std::vector outgoing_offsets, + std::vector outgoing_counts, + std::vector expected_ghosts, + std::vector expected_offsets, + std::vector expected_counts) noexcept + : topology_(std::move(topology)), + outgoing_nodes_(std::move(outgoing_nodes)), + outgoing_offsets_(std::move(outgoing_offsets)), + outgoing_counts_(std::move(outgoing_counts)), + expected_ghosts_(std::move(expected_ghosts)), + expected_offsets_(std::move(expected_offsets)), + expected_counts_(std::move(expected_counts)) {} + +auto ghost_exchange_plan::outgoing_local_nodes( + std::size_t destination_index) const noexcept -> std::span { + return segment(outgoing_nodes_, outgoing_offsets_, outgoing_counts_, + destination_index); +} + +auto ghost_exchange_plan::expected_ghost_nodes( + std::size_t source_index) const noexcept -> std::span { + return segment(expected_ghosts_, expected_offsets_, expected_counts_, + source_index); +} + +auto make_ghost_exchange_plan(parallel_graph_access const& graph) + -> std::unique_ptr { + auto const graph_communicator = mpi::communicator_view{graph.m_communicator}; + auto const rank = graph_communicator.rank(); + auto const size = graph_communicator.size(); + + auto local_structure_is_valid = + graph.m_graph_construction_complete && !graph.m_building_graph && + std::in_range(graph.m_num_local_nodes); + auto owners_by_local_node = std::vector>{}; + auto outgoing_destinations = std::vector{}; + auto outgoing_by_rank = std::vector>>{}; + auto referenced_ghosts = std::vector{}; + try { + if (local_structure_is_valid) { + auto const local_count = + static_cast(graph.m_num_local_nodes); + local_structure_is_valid = + graph.m_nodes.size() >= local_count + std::size_t{1} && + graph.m_nodes_data.size() == graph.m_nodes.size() && + graph.m_ghost_adddata_array_offset == + graph.m_num_local_nodes + NodeID{1}; + owners_by_local_node.resize(local_count); + referenced_ghosts.assign(graph.m_add_non_local_node_data.size(), + static_cast(0)); + + for (std::size_t local = 0; + local < local_count && local_structure_is_valid; ++local) { + auto const first = graph.m_nodes[local].firstEdge; + auto const last = graph.m_nodes[local + 1].firstEdge; + local_structure_is_valid = + first <= last && std::in_range(first) && + std::in_range(last) && + static_cast(last) <= graph.m_edges.size(); + if (!local_structure_is_valid) { + break; + } + + auto& owners = owners_by_local_node[local]; + for (auto edge = static_cast(first); + edge < static_cast(last); ++edge) { + auto const target = graph.m_edges[edge].local_target; + if (target < graph.m_num_local_nodes) { + continue; + } + if (target < graph.m_ghost_adddata_array_offset || + !std::in_range(target - + graph.m_ghost_adddata_array_offset)) { + local_structure_is_valid = false; + break; + } + auto const ghost_index = static_cast( + target - graph.m_ghost_adddata_array_offset); + if (ghost_index >= graph.m_add_non_local_node_data.size()) { + local_structure_is_valid = false; + break; + } + referenced_ghosts[ghost_index] = 1; + auto const owner = graph.m_add_non_local_node_data[ghost_index].peID; + if (owner < 0 || owner >= size || owner == rank) { + local_structure_is_valid = false; + break; + } + owners.push_back(owner); + } + std::ranges::sort(owners); + auto const unique_end = std::ranges::unique(owners); + owners.erase(unique_end.begin(), unique_end.end()); + outgoing_destinations.insert(outgoing_destinations.end(), + owners.begin(), owners.end()); + } + + std::ranges::sort(outgoing_destinations); + auto const unique_end = std::ranges::unique(outgoing_destinations); + outgoing_destinations.erase(unique_end.begin(), unique_end.end()); + outgoing_by_rank.reserve(outgoing_destinations.size()); + for (auto const destination : outgoing_destinations) { + outgoing_by_rank.emplace_back(destination, std::vector{}); + } + for (std::size_t local = 0; local < owners_by_local_node.size(); + ++local) { + for (auto const owner : owners_by_local_node[local]) { + auto const position = std::ranges::lower_bound( + outgoing_by_rank, owner, {}, + &std::pair>::first); + if (position == outgoing_by_rank.end() || position->first != owner) { + local_structure_is_valid = false; + continue; + } + position->second.push_back(static_cast(local)); + } + } + local_structure_is_valid = + local_structure_is_valid && + std::ranges::all_of( + referenced_ghosts, + [](unsigned char const referenced) { return referenced != 0; }); + } + } catch (...) { + mpi::abort_on_exception(graph.m_communicator, + "ghost exchange plan pre-topology allocation"); + } + + if (!mpi::detail::collective_predicate(local_structure_is_valid, + graph_communicator)) { + mpi::throw_collectively_agreed_semantic_error( + graph.m_communicator, "ghost exchange plan graph validation failed"); + } + + auto semantic_failure = false; + auto result = std::unique_ptr{}; + { + auto topology = + mpi::distributed_graph{graph_communicator, outgoing_destinations}; + try { + auto local_plan_is_valid = true; + auto sorted_sources = std::vector{topology.sources().begin(), + topology.sources().end()}; + auto sorted_destinations = std::vector{ + topology.destinations().begin(), topology.destinations().end()}; + std::ranges::sort(sorted_sources); + std::ranges::sort(sorted_destinations); + local_plan_is_valid = sorted_sources == sorted_destinations; + + auto outgoing_segments = + std::vector>(topology.destinations().size()); + for (std::size_t index = 0; index < topology.destinations().size(); + ++index) { + auto const destination = topology.destinations()[index]; + auto const position = std::ranges::lower_bound( + outgoing_by_rank, destination, {}, + &std::pair>::first); + if (position == outgoing_by_rank.end() || + position->first != destination) { + local_plan_is_valid = false; + continue; + } + outgoing_segments[index] = position->second; + local_plan_is_valid = + local_plan_is_valid && + std::ranges::is_sorted(outgoing_segments[index]) && + std::ranges::adjacent_find(outgoing_segments[index]) == + outgoing_segments[index].end(); + } + + auto expected_segments = + std::vector>(topology.sources().size()); + auto const ghost_offset = graph.m_ghost_adddata_array_offset; + local_plan_is_valid = + local_plan_is_valid && std::in_range(ghost_offset) && + static_cast(ghost_offset) <= graph.m_nodes.size() && + graph.m_nodes.size() - static_cast(ghost_offset) == + graph.m_add_non_local_node_data.size() && + graph.m_global_to_local_id.size() == + graph.m_add_non_local_node_data.size(); + + for (std::size_t ghost_index = 0; + ghost_index < graph.m_add_non_local_node_data.size(); + ++ghost_index) { + auto const& metadata = graph.m_add_non_local_node_data[ghost_index]; + auto const source_index = topology.source_index(metadata.peID); + if (!source_index.has_value() || metadata.peID < 0 || + metadata.peID >= size || !std::in_range(ghost_index) || + ghost_offset > std::numeric_limits::max() - + static_cast(ghost_index)) { + local_plan_is_valid = false; + continue; + } + auto const local_id = ghost_offset + static_cast(ghost_index); + auto const mapping = graph.m_global_to_local_id.find(metadata.globalID); + if (mapping == graph.m_global_to_local_id.end() || + mapping->second != local_id) { + local_plan_is_valid = false; + continue; + } + expected_segments[*source_index].push_back(metadata.globalID); + } + for (auto& expected : expected_segments) { + std::ranges::sort(expected); + if (std::ranges::adjacent_find(expected) != expected.end()) { + local_plan_is_valid = false; + } + } + + auto outgoing = flat_segments{}; + auto expected = flat_segments{}; + if (local_plan_is_valid) { + outgoing = flatten(outgoing_segments); + expected = flatten(expected_segments); + } + + if (!mpi::detail::collective_predicate(local_plan_is_valid, + topology.view())) { + semantic_failure = true; + } else { + result = std::unique_ptr{new ghost_exchange_plan{ + std::move(topology), std::move(outgoing.storage), + std::move(outgoing.offsets), std::move(outgoing.counts), + std::move(expected.storage), std::move(expected.offsets), + std::move(expected.counts)}}; + if (!mpi::detail::collective_predicate(result != nullptr, + result->topology().view())) { + mpi::abort_on_programming_error( + result->topology().native_handle(), + "ghost exchange plan readiness diverged across ranks"); + } + } + } catch (...) { + auto const affected = result == nullptr + ? topology.native_handle() + : result->topology().native_handle(); + mpi::abort_on_exception(affected, + "ghost exchange plan post-topology failure"); + } + } + + if (semantic_failure) { + mpi::throw_collectively_agreed_semantic_error( + graph.m_communicator, + "ghost exchange plan semantic validation failed"); + } + return result; +} +} // namespace parhip diff --git a/parallel/parallel_src/lib/communication/ghost_exchange_plan.h b/parallel/parallel_src/lib/communication/ghost_exchange_plan.h new file mode 100644 index 00000000..17c35317 --- /dev/null +++ b/parallel/parallel_src/lib/communication/ghost_exchange_plan.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include +#include + +#include "communication/mpi_neighbors.h" +#include "definitions.h" + +namespace parhip { +class parallel_graph_access; + +class ghost_exchange_plan final { + public: + ghost_exchange_plan(ghost_exchange_plan const&) = delete; + auto operator=(ghost_exchange_plan const&) -> ghost_exchange_plan& = delete; + ghost_exchange_plan(ghost_exchange_plan&&) = delete; + auto operator=(ghost_exchange_plan&&) -> ghost_exchange_plan& = delete; + + [[nodiscard]] auto topology() const noexcept + -> mpi::distributed_graph const& { + return topology_; + } + [[nodiscard]] auto outgoing_local_nodes( + std::size_t destination_index) const noexcept -> std::span; + [[nodiscard]] auto expected_ghost_nodes( + std::size_t source_index) const noexcept -> std::span; + + private: + friend class parallel_graph_access; + friend auto make_ghost_exchange_plan(parallel_graph_access const& graph) + -> std::unique_ptr; + + ghost_exchange_plan(mpi::distributed_graph topology, + std::vector outgoing_nodes, + std::vector outgoing_offsets, + std::vector outgoing_counts, + std::vector expected_ghosts, + std::vector expected_offsets, + std::vector expected_counts) noexcept; + + mpi::distributed_graph topology_; + std::vector outgoing_nodes_; + std::vector outgoing_offsets_; + std::vector outgoing_counts_; + std::vector expected_ghosts_; + std::vector expected_offsets_; + std::vector expected_counts_; +}; + +[[nodiscard]] auto make_ghost_exchange_plan(parallel_graph_access const& graph) + -> std::unique_ptr; +} // namespace parhip diff --git a/parallel/parallel_src/lib/communication/ghost_label_update.h b/parallel/parallel_src/lib/communication/ghost_label_update.h new file mode 100644 index 00000000..b3bc7609 --- /dev/null +++ b/parallel/parallel_src/lib/communication/ghost_label_update.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +#include "communication/mpi_types.h" +#include "definitions.h" + +namespace parhip { +struct ghost_label_update final { + NodeID global_id; + NodeID label; + + auto operator==(ghost_label_update const&) const -> bool = default; +}; + +static_assert(std::is_standard_layout_v); +static_assert(std::is_trivially_copyable_v); +} // namespace parhip + +template <> +struct parhip::mpi::wire_members { + inline static constexpr auto value = std::tuple{ + &parhip::ghost_label_update::global_id, + &parhip::ghost_label_update::label}; +}; diff --git a/parallel/parallel_src/lib/communication/implicit_lifetime.h b/parallel/parallel_src/lib/communication/implicit_lifetime.h new file mode 100644 index 00000000..3e27610f --- /dev/null +++ b/parallel/parallel_src/lib/communication/implicit_lifetime.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +namespace parhip::mpi::detail { +namespace implicit_lifetime_detail { +template +inline constexpr bool fallback_is_implicit_lifetime_v = [] { + using value_type = std::remove_cv_t; + + if constexpr (std::is_array_v) { + return fallback_is_implicit_lifetime_v>; + } else if constexpr (std::is_scalar_v) { + return true; + } else if constexpr (std::is_class_v || + std::is_union_v) { + // P2674: an implicit-lifetime class is either an aggregate with a + // non-user-provided destructor, or has a trivial eligible constructor and + // a trivial, non-deleted destructor. Within KaHIP's trivially-copyable + // storage contract, trivially destructible is a conservative proxy for + // the aggregate destructor rule. The constructibility traits are likewise + // conservative because inaccessible trivial constructors report false. + return std::is_trivially_destructible_v && + (std::is_aggregate_v || + std::is_trivially_default_constructible_v || + std::is_trivially_copy_constructible_v || + std::is_trivially_move_constructible_v); + } else { + return false; + } +}(); +} // namespace implicit_lifetime_detail + +template +inline constexpr bool is_implicit_lifetime_v = +#if defined(__cpp_lib_is_implicit_lifetime) && \ + __cpp_lib_is_implicit_lifetime >= 202302L + std::is_implicit_lifetime_v>; +#else + implicit_lifetime_detail::fallback_is_implicit_lifetime_v; +#endif +} // namespace parhip::mpi::detail diff --git a/parallel/parallel_src/lib/communication/mpi_adapter.cpp b/parallel/parallel_src/lib/communication/mpi_adapter.cpp new file mode 100644 index 00000000..bd9c4560 --- /dev/null +++ b/parallel/parallel_src/lib/communication/mpi_adapter.cpp @@ -0,0 +1,94 @@ +#include "communication/mpi_adapter.h" + +#include + +namespace parhip::mpi { +communicator::communicator(communicator_view source) { + require_live_intracommunicator( + source, + "communicator duplication requires a live intracommunicator"); + check_or_abort(MPI_Comm_dup(source.native_handle(), &communicator_), + source.native_handle(), + "MPI_Comm_dup"); + auto const handler_result = + MPI_Comm_set_errhandler(communicator_, MPI_ERRORS_RETURN); + if (handler_result != MPI_SUCCESS) { + abort_on_mpi_error( + communicator_, handler_result, "MPI_Comm_set_errhandler"); + } +} + +communicator::~communicator() noexcept { reset(); } + +communicator::communicator(communicator&& other) noexcept + : communicator_(std::exchange(other.communicator_, MPI_COMM_NULL)) {} + +auto communicator::operator=(communicator&& other) noexcept -> communicator& { + if (this != &other) { + reset(); + communicator_ = std::exchange(other.communicator_, MPI_COMM_NULL); + } + return *this; +} + +void communicator::reset() noexcept { + if (communicator_ != MPI_COMM_NULL) { + if (!runtime_is_active()) { + abort_on_inactive_mpi_ownership("communicator destruction"); + } + auto const free_result = MPI_Comm_free(&communicator_); + if (free_result != MPI_SUCCESS) { + // MPI_Comm_free may already have invalidated the saved handle. The + // process group is still known through MPI_COMM_WORLD, so fail fast + // there instead of issuing MPI_Abort on a possibly stale communicator. + abort_on_mpi_error(MPI_COMM_WORLD, free_result, "MPI_Comm_free"); + } + } + communicator_ = MPI_COMM_NULL; +} + +topology::topology(communicator_view source) : communicator_(source) { + int topology_kind = MPI_UNDEFINED; + check_or_abort(MPI_Topo_test(communicator_.native_handle(), &topology_kind), + communicator_.native_handle(), + "MPI_Topo_test"); + if (topology_kind == MPI_UNDEFINED) { + abort_on_programming_error( + communicator_.native_handle(), + "topology requires an MPI topology communicator"); + } +} + +datatype::~datatype() noexcept { reset(); } + +datatype::datatype(datatype&& other) noexcept + : handle_(std::exchange(other.handle_, MPI_DATATYPE_NULL)), + owns_(std::exchange(other.owns_, false)), + failure_communicator_( + std::exchange(other.failure_communicator_, MPI_COMM_WORLD)) {} + +auto datatype::operator=(datatype&& other) noexcept -> datatype& { + if (this != &other) { + reset(); + handle_ = std::exchange(other.handle_, MPI_DATATYPE_NULL); + owns_ = std::exchange(other.owns_, false); + failure_communicator_ = + std::exchange(other.failure_communicator_, MPI_COMM_WORLD); + } + return *this; +} + +void datatype::reset() noexcept { + if (owns_ && handle_ != MPI_DATATYPE_NULL) { + if (!runtime_is_active()) { + abort_on_inactive_mpi_ownership("owned datatype destruction"); + } + check_or_abort(MPI_Type_free(&handle_), + failure_communicator_, + "MPI_Type_free"); + } + handle_ = MPI_DATATYPE_NULL; + owns_ = false; + failure_communicator_ = MPI_COMM_WORLD; +} +} // namespace parhip::mpi diff --git a/parallel/parallel_src/lib/communication/mpi_adapter.h b/parallel/parallel_src/lib/communication/mpi_adapter.h new file mode 100644 index 00000000..0247801e --- /dev/null +++ b/parallel/parallel_src/lib/communication/mpi_adapter.h @@ -0,0 +1,10 @@ +#pragma once + +#include "communication/mpi_collectives.h" +#include "communication/mpi_async_neighbors.h" +#include "communication/mpi_error.h" +#include "communication/mpi_failure.h" +#include "communication/mpi_handles.h" +#include "communication/mpi_neighbors.h" +#include "communication/mpi_types.h" +#include "communication/segmented_buffer.h" diff --git a/parallel/parallel_src/lib/communication/mpi_application.cpp b/parallel/parallel_src/lib/communication/mpi_application.cpp new file mode 100644 index 00000000..b978a083 --- /dev/null +++ b/parallel/parallel_src/lib/communication/mpi_application.cpp @@ -0,0 +1,47 @@ +#include "communication/mpi_application.h" + +#include +#include + +#include "tools/fatal_diagnostics.h" + +namespace parhip::mpi { +namespace { +[[noreturn]] void abort_on_unusable_runtime(int error_code, + std::string_view boundary, + std::string_view operation, + int rank) noexcept { + if (rank >= 0) { + kahip::diagnostics::critical( + "MPI lifecycle failure: ", boundary, ": ", operation, + " returned raw error ", error_code, " on rank ", rank); + } else { + kahip::diagnostics::critical( + "MPI lifecycle failure: ", boundary, ": ", operation, + " returned raw error ", error_code, " (rank unavailable)"); + } + std::abort(); +} +} // namespace + +application_runtime::application_runtime(int& argument_count, + char**& argument_values, + std::string_view boundary) + : boundary_(boundary) { + auto const result = MPI_Init(&argument_count, &argument_values); + if (result != MPI_SUCCESS) { + abort_on_unusable_runtime(result, boundary_, "MPI_Init", -1); + } + check_or_abort(MPI_Comm_rank(MPI_COMM_WORLD, &rank_), MPI_COMM_WORLD, + "MPI_Comm_rank(application runtime)"); +} + +application_runtime::~application_runtime() noexcept { + auto const result = MPI_Finalize(); + if (result != MPI_SUCCESS) { + // MPI_Finalize may have partially dismantled the runtime. Issuing any + // further MPI call, including MPI_Abort, is not portable here. + abort_on_unusable_runtime(result, boundary_, "MPI_Finalize", rank_); + } +} +} // namespace parhip::mpi diff --git a/parallel/parallel_src/lib/communication/mpi_application.h b/parallel/parallel_src/lib/communication/mpi_application.h new file mode 100644 index 00000000..48202bc7 --- /dev/null +++ b/parallel/parallel_src/lib/communication/mpi_application.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_failure.h" +#include "communication/mpi_handles.h" + +namespace parhip::mpi { +// Executables own the MPI runtime. Libraries only receive the duplicated +// operation communicator created by execute(), and all objects using that +// communicator are destroyed before this runtime finalizes MPI. +class application_runtime final { + public: + application_runtime(int& argument_count, + char**& argument_values, + std::string_view boundary); + ~application_runtime() noexcept; + + application_runtime(application_runtime const&) = delete; + auto operator=(application_runtime const&) -> application_runtime& = delete; + application_runtime(application_runtime&&) = delete; + auto operator=(application_runtime&&) -> application_runtime& = delete; + + template + requires std::invocable && + std::same_as, + int> + [[nodiscard]] auto execute(Operation&& operation) noexcept -> int { + communicator operation_communicator{communicator_view{MPI_COMM_WORLD}}; + try { + return std::invoke(std::forward(operation), + operation_communicator.view()); + } catch (...) { + abort_on_exception(operation_communicator.native_handle(), boundary_, + std::current_exception()); + } + } + + private: + std::string boundary_; + int rank_ = -1; +}; +} // namespace parhip::mpi diff --git a/parallel/parallel_src/lib/communication/mpi_async_neighbors.h b/parallel/parallel_src/lib/communication/mpi_async_neighbors.h new file mode 100644 index 00000000..5cd39b3e --- /dev/null +++ b/parallel/parallel_src/lib/communication/mpi_async_neighbors.h @@ -0,0 +1,1058 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_neighbors.h" +#include "kahip_mpi_capabilities.h" + +namespace parhip::mpi { +enum class persistence_policy : std::uint8_t { + disabled, + prefer, + required, +}; + +struct context_options { + collective_options collective{}; + persistence_policy persistence = persistence_policy::disabled; +}; + +namespace detail { +enum class neighbor_direct_backend : std::uint8_t { + bounded_legacy, + immediate_legacy, + immediate_large_count, + persistent_legacy, + persistent_large_count, +}; + +[[nodiscard]] inline auto is_persistent( + neighbor_direct_backend backend) noexcept -> bool { + return backend == neighbor_direct_backend::persistent_legacy || + backend == neighbor_direct_backend::persistent_large_count; +} + +[[nodiscard]] inline auto uses_large_count( + neighbor_direct_backend backend) noexcept -> bool { + return backend == neighbor_direct_backend::immediate_large_count || + backend == neighbor_direct_backend::persistent_large_count; +} + +inline auto validate_persistence_policy(persistence_policy policy, + communicator_view communicator) + -> std::optional { + auto const encoded = static_cast(policy); + auto const locally_valid = + encoded <= static_cast(persistence_policy::required); + auto const local = static_cast(encoded); + auto minimum = std::uint64_t{0}; + auto maximum = std::uint64_t{0}; + check_or_abort(MPI_Allreduce(&local, &minimum, 1, MPI_UINT64_T, MPI_MIN, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(neighbor persistence policy minimum)"); + check_or_abort(MPI_Allreduce(&local, &maximum, 1, MPI_UINT64_T, MPI_MAX, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(neighbor persistence policy maximum)"); + if (!collective_predicate(locally_valid, communicator) || + minimum != maximum) { + return std::nullopt; + } + return policy; +} + +using neighbor_backend_mask = std::uint64_t; + +[[nodiscard]] constexpr auto backend_bit( + neighbor_direct_backend backend) noexcept -> neighbor_backend_mask { + return neighbor_backend_mask{1} << static_cast(backend); +} + +struct neighbor_backend_masks final { + neighbor_backend_mask allowed = 0; + neighbor_backend_mask physical = 0; + + auto operator==(neighbor_backend_masks const&) const -> bool = default; +}; + +[[nodiscard]] constexpr auto compiled_backend_mask(bool force_mpi3) noexcept + -> neighbor_backend_mask { + auto result = neighbor_backend_mask{0}; + if constexpr (KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV != 0) { + result |= backend_bit(neighbor_direct_backend::immediate_legacy); + } + if (!force_mpi3) { + if constexpr (KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV_C != 0) { + result |= backend_bit(neighbor_direct_backend::immediate_large_count); + } + if constexpr (KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT != 0) { + result |= backend_bit(neighbor_direct_backend::persistent_legacy); + } + if constexpr (KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT_C != 0) { + result |= backend_bit(neighbor_direct_backend::persistent_large_count); + } + } + return result; +} + +[[nodiscard]] constexpr auto policy_backend_mask( + persistence_policy policy) noexcept -> neighbor_backend_mask { + constexpr auto immediate = + backend_bit(neighbor_direct_backend::immediate_legacy) | + backend_bit(neighbor_direct_backend::immediate_large_count); + constexpr auto persistent = + backend_bit(neighbor_direct_backend::persistent_legacy) | + backend_bit(neighbor_direct_backend::persistent_large_count); + switch (policy) { + case persistence_policy::disabled: + return immediate; + case persistence_policy::prefer: + return persistent | immediate; + case persistence_policy::required: + return persistent; + } + return 0; +} + +[[nodiscard]] constexpr auto choose_direct_backend( + neighbor_backend_mask common_allowed) noexcept + -> std::optional { + constexpr auto precedence = std::array{ + neighbor_direct_backend::persistent_large_count, + neighbor_direct_backend::persistent_legacy, + neighbor_direct_backend::immediate_large_count, + neighbor_direct_backend::immediate_legacy, + }; + auto const selected = std::ranges::find_if(precedence, [&](auto backend) { + return (common_allowed & backend_bit(backend)) != 0; + }); + return selected == precedence.end() + ? std::nullopt + : std::optional{*selected}; +} + +[[nodiscard]] inline auto agree_neighbor_backend_masks( + neighbor_backend_masks local, + communicator_view communicator) -> neighbor_backend_masks { + auto const local_masks = std::array{local.allowed, local.physical}; + auto common_masks = std::array{}; + check_or_abort( + MPI_Allreduce(local_masks.data(), common_masks.data(), + static_cast(common_masks.size()), MPI_UINT64_T, + MPI_BAND, communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(direct neighborhood backend agreement)"); + return neighbor_backend_masks{ + .allowed = common_masks[0], + .physical = common_masks[1], + }; +} + +template +struct pending_neighbor_sends final { + std::optional> materialized; + std::vector fixed_counts; + std::vector fixed_offsets; + std::size_t fixed_element_count = 0; + capacity_result capacity; + + [[nodiscard]] static auto one_shot(segmented_buffer sends) + -> pending_neighbor_sends { + auto result = pending_neighbor_sends{}; + result.materialized.emplace(std::move(sends)); + return result; + } + + [[nodiscard]] static auto fixed(std::vector counts) + -> pending_neighbor_sends { + auto result = pending_neighbor_sends{}; + result.fixed_counts = std::move(counts); + return result; + } + + [[nodiscard]] auto is_fixed() const noexcept -> bool { + return !materialized.has_value(); + } + + [[nodiscard]] auto locally_valid(std::size_t expected_segments) const noexcept + -> bool { + return is_fixed() ? fixed_counts.size() == expected_segments + : materialized->has_canonical_layout(expected_segments); + } + + void prepare_fixed_layout() { + if (!is_fixed()) { + return; + } + auto layout = canonical_neighbor_layout(fixed_counts); + fixed_offsets = std::move(layout.offsets); + fixed_element_count = layout.element_count; + capacity = layout.capacity; + if (fixed_element_count > + std::numeric_limits::max() / sizeof(T)) { + capacity = with_fatal_capacity_issue( + capacity, capacity_issue::storage_byte_size_overflow); + } + } + + [[nodiscard]] auto counts() const noexcept -> std::span { + return is_fixed() ? std::span{fixed_counts} + : std::span{materialized->counts()}; + } + + [[nodiscard]] auto offsets() const noexcept -> std::span { + return is_fixed() ? std::span{fixed_offsets} + : std::span{materialized->offsets()}; + } + + [[nodiscard]] auto materialize() -> segmented_buffer { + if (materialized.has_value()) { + return std::move(*materialized); + } + return segmented_buffer::uninitialized( + fixed_element_count, std::move(fixed_counts), std::move(fixed_offsets)); + } +}; + +struct legacy_neighbor_layout final { + std::vector send_counts; + std::vector send_offsets; + std::vector receive_counts; + std::vector receive_offsets; +}; + +struct large_count_neighbor_layout final { + std::vector send_counts; + std::vector send_offsets; + std::vector receive_counts; + std::vector receive_offsets; +}; + +using direct_neighbor_layout = + std::variant; + +static_assert(std::is_nothrow_move_constructible_v); +static_assert(std::is_nothrow_move_assignable_v); + +[[nodiscard]] inline auto checked_neighbor_mpi_count(std::size_t value, + std::string_view context) + -> MPI_Count { + if (!std::in_range(value)) { + throw mpi_error{MPI_ERR_COUNT, std::string{context}}; + } + return static_cast(value); +} + +[[nodiscard]] inline auto checked_neighbor_mpi_aint(std::size_t value, + std::string_view context) + -> MPI_Aint { + if (!std::in_range(value)) { + throw mpi_error{MPI_ERR_COUNT, std::string{context}}; + } + return static_cast(value); +} + +template +[[nodiscard]] auto convert_neighbor_layout(std::span values, + Conversion conversion) + -> std::vector { + auto result = std::vector{}; + result.reserve(values.size()); + std::ranges::transform(values, std::back_inserter(result), conversion); + return result; +} + +[[nodiscard]] inline auto build_direct_neighbor_layout( + neighbor_direct_backend backend, + std::span send_counts, + std::span send_offsets, + std::span receive_counts, + std::span receive_offsets) -> direct_neighbor_layout { + if (uses_large_count(backend)) { + return large_count_neighbor_layout{ + .send_counts = convert_neighbor_layout( + send_counts, + [](auto value) { + return checked_neighbor_mpi_count(value, "neighbor send count"); + }), + .send_offsets = convert_neighbor_layout( + send_offsets, + [](auto value) { + return checked_neighbor_mpi_aint(value, "neighbor send offset"); + }), + .receive_counts = convert_neighbor_layout( + receive_counts, + [](auto value) { + return checked_neighbor_mpi_count(value, + "neighbor receive count"); + }), + .receive_offsets = convert_neighbor_layout( + receive_offsets, + [](auto value) { + return checked_neighbor_mpi_aint(value, + "neighbor receive offset"); + }), + }; + } + return legacy_neighbor_layout{ + .send_counts = convert_neighbor_layout( + send_counts, + [](auto value) { return checked_int(value, "neighbor send count"); }), + .send_offsets = convert_neighbor_layout( + send_offsets, + [](auto value) { + return checked_int(value, "neighbor send offset"); + }), + .receive_counts = convert_neighbor_layout( + receive_counts, + [](auto value) { + return checked_int(value, "neighbor receive count"); + }), + .receive_offsets = convert_neighbor_layout( + receive_offsets, + [](auto value) { + return checked_int(value, "neighbor receive offset"); + }), + }; +} + +template +struct direct_neighbor_storage { + direct_neighbor_storage(communicator operation_communicator, + datatype operation_datatype, + segmented_buffer send_buffer, + segmented_buffer receive_buffer, + direct_neighbor_layout layout, + neighbor_direct_backend selected_backend, + std::optional + bounded_plan = std::nullopt) + : communicator_(std::move(operation_communicator)), + datatype_(std::move(operation_datatype)), + sends_(std::move(send_buffer)), + received_(std::move(receive_buffer)), + layout_(std::move(layout)), + backend_(selected_backend), + bounded_plan_(std::move(bounded_plan)), + bounded_send_counts_(sends_.segment_count(), 0), + bounded_receive_counts_(received_.segment_count(), 0), + bounded_send_displacements_(sends_.segment_count(), 0), + bounded_receive_displacements_(received_.segment_count(), 0) {} + + direct_neighbor_storage(direct_neighbor_storage const&) = delete; + auto operator=(direct_neighbor_storage const&) + -> direct_neighbor_storage& = delete; + direct_neighbor_storage(direct_neighbor_storage&&) = delete; + auto operator=(direct_neighbor_storage&&) + -> direct_neighbor_storage& = delete; + + [[nodiscard]] auto view() const noexcept -> communicator_view { + return communicator_.view(); + } + + [[nodiscard]] auto send_buffer() const noexcept -> void const* { + auto const storage = sends_.storage(); + return storage.empty() + ? static_cast(std::addressof(ignored_send_byte_)) + : static_cast(storage.data()); + } + + [[nodiscard]] auto receive_buffer() noexcept -> void* { + auto storage = received_.storage(); + return storage.empty() + ? static_cast(std::addressof(ignored_receive_byte_)) + : static_cast(storage.data()); + } + + template + [[nodiscard]] static auto data_or_ignored(std::vector const& values, + Value const& ignored) noexcept + -> Value const* { + return values.empty() ? std::addressof(ignored) : values.data(); + } + + [[nodiscard]] auto legacy_layout() const noexcept + -> legacy_neighbor_layout const* { + return std::get_if(&layout_); + } + + [[nodiscard]] auto large_count_layout() const noexcept + -> large_count_neighbor_layout const* { + return std::get_if(&layout_); + } + + [[nodiscard]] auto is_bounded() const noexcept -> bool { + return backend_ == neighbor_direct_backend::bounded_legacy; + } + + void seek_next_bounded_round() noexcept { + while (bounded_phase_ < bounded_plan_->phases.size() && + bounded_round_ >= + bounded_plan_->phases[bounded_phase_].round_count) { + ++bounded_phase_; + bounded_round_ = 0; + } + } + + [[nodiscard]] auto has_bounded_round() const noexcept -> bool { + return bounded_phase_ < bounded_plan_->phases.size(); + } + + void launch_bounded_round() noexcept { + std::ranges::fill(bounded_send_counts_, 0); + std::ranges::fill(bounded_receive_counts_, 0); + auto const round = make_mpi3_bounded_neighbor_round( + *bounded_plan_, bounded_phase_, bounded_round_, sends_.offsets(), + received_.offsets()); + if (round.destination_index.has_value()) { + bounded_send_counts_[*round.destination_index] = round.send_count; + } + if (round.source_index.has_value()) { + bounded_receive_counts_[*round.source_index] = round.receive_count; + } + auto const* round_send_buffer = + round.send_storage_offset.has_value() + ? static_cast(sends_.storage().data() + + *round.send_storage_offset) + : send_buffer(); + auto* round_receive_buffer = + round.receive_storage_offset.has_value() + ? static_cast(received_.storage().data() + + *round.receive_storage_offset) + : receive_buffer(); + check_or_abort( + MPI_Ineighbor_alltoallv( + round_send_buffer, + data_or_ignored(bounded_send_counts_, ignored_int_), + data_or_ignored(bounded_send_displacements_, ignored_int_), + datatype_.native_handle(), round_receive_buffer, + data_or_ignored(bounded_receive_counts_, ignored_int_), + data_or_ignored(bounded_receive_displacements_, ignored_int_), + datatype_.native_handle(), communicator_.native_handle(), + &request_), + communicator_.native_handle(), + "MPI_Ineighbor_alltoallv(MPI-3 bounded neighborhood round)"); + } + + void begin_bounded_generation() noexcept { + if (!bounded_plan_.has_value()) { + abort_on_programming_error( + communicator_.native_handle(), + "bounded neighborhood backend requires a bounded MPI-3 plan"); + } + bounded_phase_ = 0; + bounded_round_ = 0; + seek_next_bounded_round(); + if (!has_bounded_round()) { + active_ = false; + receive_ready_ = true; + return; + } + launch_bounded_round(); + } + + void advance_bounded_round() noexcept { + ++bounded_round_; + seek_next_bounded_round(); + if (has_bounded_round()) { + launch_bounded_round(); + return; + } + active_ = false; + receive_ready_ = true; + } + + [[nodiscard]] auto test_bounded_generation() noexcept -> bool { + auto complete = 0; + check_or_abort(MPI_Test(&request_, &complete, MPI_STATUS_IGNORE), + communicator_.native_handle(), + "MPI_Test(MPI-3 bounded neighborhood round)"); + if (complete == 0) { + return false; + } + advance_bounded_round(); + return !active_; + } + + void wait_bounded_generation() noexcept { + while (active_) { + check_or_abort(MPI_Wait(&request_, MPI_STATUS_IGNORE), + communicator_.native_handle(), + "MPI_Wait(MPI-3 bounded neighborhood round)"); + advance_bounded_round(); + } + } + + void initiate_immediate() noexcept { + active_ = true; + receive_ready_ = false; + if (is_bounded()) { + begin_bounded_generation(); + return; + } + if (backend_ == neighbor_direct_backend::immediate_legacy) { + auto const* layout = legacy_layout(); + if (layout == nullptr) { + abort_on_programming_error( + communicator_.native_handle(), + "legacy neighborhood backend requires a legacy MPI layout"); + } + check_or_abort( + MPI_Ineighbor_alltoallv( + send_buffer(), data_or_ignored(layout->send_counts, ignored_int_), + data_or_ignored(layout->send_offsets, ignored_int_), + datatype_.native_handle(), receive_buffer(), + data_or_ignored(layout->receive_counts, ignored_int_), + data_or_ignored(layout->receive_offsets, ignored_int_), + datatype_.native_handle(), communicator_.native_handle(), + &request_), + communicator_.native_handle(), + "MPI_Ineighbor_alltoallv(immediate neighborhood exchange)"); + return; + } +#if KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV_C + if (backend_ == neighbor_direct_backend::immediate_large_count) { + auto const* layout = large_count_layout(); + if (layout == nullptr) { + abort_on_programming_error( + communicator_.native_handle(), + "large-count neighborhood backend requires a large-count MPI " + "layout"); + } + check_or_abort( + MPI_Ineighbor_alltoallv_c( + send_buffer(), + data_or_ignored(layout->send_counts, ignored_count_), + data_or_ignored(layout->send_offsets, ignored_aint_), + datatype_.native_handle(), receive_buffer(), + data_or_ignored(layout->receive_counts, ignored_count_), + data_or_ignored(layout->receive_offsets, ignored_aint_), + datatype_.native_handle(), communicator_.native_handle(), + &request_), + communicator_.native_handle(), + "MPI_Ineighbor_alltoallv_c(immediate neighborhood exchange)"); + return; + } +#endif + abort_on_programming_error(communicator_.native_handle(), + "immediate initiation selected a persistent " + "neighborhood backend"); + } + + void initialize_persistent() noexcept { + if (backend_ == neighbor_direct_backend::persistent_legacy) { +#if KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT + auto const* layout = legacy_layout(); + if (layout == nullptr) { + abort_on_programming_error( + communicator_.native_handle(), + "legacy neighborhood backend requires a legacy MPI layout"); + } + check_or_abort( + MPI_Neighbor_alltoallv_init( + send_buffer(), data_or_ignored(layout->send_counts, ignored_int_), + data_or_ignored(layout->send_offsets, ignored_int_), + datatype_.native_handle(), receive_buffer(), + data_or_ignored(layout->receive_counts, ignored_int_), + data_or_ignored(layout->receive_offsets, ignored_int_), + datatype_.native_handle(), communicator_.native_handle(), + MPI_INFO_NULL, &request_), + communicator_.native_handle(), + "MPI_Neighbor_alltoallv_init(persistent neighborhood exchange)"); + return; +#endif + } +#if KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT_C + if (backend_ == neighbor_direct_backend::persistent_large_count) { + auto const* layout = large_count_layout(); + if (layout == nullptr) { + abort_on_programming_error( + communicator_.native_handle(), + "large-count neighborhood backend requires a large-count MPI " + "layout"); + } + check_or_abort( + MPI_Neighbor_alltoallv_init_c( + send_buffer(), + data_or_ignored(layout->send_counts, ignored_count_), + data_or_ignored(layout->send_offsets, ignored_aint_), + datatype_.native_handle(), receive_buffer(), + data_or_ignored(layout->receive_counts, ignored_count_), + data_or_ignored(layout->receive_offsets, ignored_aint_), + datatype_.native_handle(), communicator_.native_handle(), + MPI_INFO_NULL, &request_), + communicator_.native_handle(), + "MPI_Neighbor_alltoallv_init_c(persistent neighborhood exchange)"); + return; + } +#endif + abort_on_programming_error(communicator_.native_handle(), + "persistent initialization selected an " + "unavailable neighborhood backend"); + } + + void start_generation() noexcept { + require_active_runtime("neighborhood operation start"); + if (active_) { + abort_on_programming_error(communicator_.native_handle(), + "neighbor context start requires an " + "inactive generation"); + } + receive_ready_ = false; + active_ = true; + if (is_persistent(backend_)) { + check_or_abort(MPI_Start(&request_), communicator_.native_handle(), + "MPI_Start(persistent neighborhood exchange)"); + return; + } + initiate_immediate(); + } + + [[nodiscard]] auto test_generation() noexcept -> bool { + require_active_runtime("neighborhood operation test"); + if (!active_) { + abort_on_programming_error(communicator_.native_handle(), + "neighbor context test requires an active " + "generation"); + } + if (is_bounded()) { + return test_bounded_generation(); + } + auto complete = 0; + check_or_abort(MPI_Test(&request_, &complete, MPI_STATUS_IGNORE), + communicator_.native_handle(), + "MPI_Test(neighborhood exchange)"); + if (complete != 0) { + active_ = false; + receive_ready_ = true; + } + return complete != 0; + } + + void wait_generation() noexcept { + require_active_runtime("neighborhood operation wait"); + if (!active_) { + abort_on_programming_error(communicator_.native_handle(), + "neighbor context wait requires an active " + "generation"); + } + if (is_bounded()) { + wait_bounded_generation(); + return; + } + check_or_abort(MPI_Wait(&request_, MPI_STATUS_IGNORE), + communicator_.native_handle(), + "MPI_Wait(neighborhood exchange)"); + active_ = false; + receive_ready_ = true; + } + + void complete_active_for_destruction() noexcept { + require_active_runtime("neighborhood operation destruction"); + if (active_) { + if (is_bounded()) { + wait_bounded_generation(); + return; + } + check_or_abort(MPI_Wait(&request_, MPI_STATUS_IGNORE), + communicator_.native_handle(), + "MPI_Wait(active neighborhood destruction)"); + active_ = false; + receive_ready_ = true; + } + } + + void release_persistent_request() noexcept { + if (!is_persistent(backend_)) { + return; + } + if (active_) { + abort_on_programming_error(communicator_.native_handle(), + "persistent request free requires an " + "inactive generation"); + } + check_or_abort(MPI_Request_free(&request_), communicator_.native_handle(), + "MPI_Request_free(persistent neighborhood exchange)"); + } + + void require_active_runtime(std::string_view context) const noexcept { + if (!runtime_is_active()) { + abort_on_inactive_mpi_ownership(context); + } + } + + [[nodiscard]] auto send_segment(std::size_t index) noexcept -> std::span { + require_active_runtime("neighborhood send-buffer access"); + if (active_ || index >= sends_.segment_count()) { + abort_on_programming_error(communicator_.native_handle(), + active_ ? "neighbor context send mutation " + "requires an inactive generation" + : "neighbor context send segment " + "index is out of range"); + } + return sends_.segment(index); + } + + [[nodiscard]] auto received_segment(std::size_t index) const noexcept + -> std::span { + require_active_runtime("neighborhood receive-buffer access"); + if (!receive_ready_ || active_ || index >= received_.segment_count()) { + abort_on_programming_error( + communicator_.native_handle(), + !receive_ready_ || active_ + ? "neighbor context receive access requires a completed " + "generation" + : "neighbor context receive segment index is out of range"); + } + return received_.segment(index); + } + + communicator communicator_; + datatype datatype_; + segmented_buffer sends_; + segmented_buffer received_; + direct_neighbor_layout layout_; + neighbor_direct_backend backend_; + std::optional bounded_plan_; + std::vector bounded_send_counts_; + std::vector bounded_receive_counts_; + std::vector bounded_send_displacements_; + std::vector bounded_receive_displacements_; + std::size_t bounded_phase_ = 0; + std::size_t bounded_round_ = 0; + MPI_Request request_ = MPI_REQUEST_NULL; + bool active_ = false; + bool receive_ready_ = false; + int ignored_int_ = 0; + MPI_Count ignored_count_ = 0; + MPI_Aint ignored_aint_ = 0; + std::byte ignored_send_byte_{}; + std::byte ignored_receive_byte_{}; +}; + +[[nodiscard]] constexpr auto filter_local_backend_masks( + neighbor_backend_mask available, + persistence_policy persistence, + bool policy_legacy_representable, + bool physical_legacy_representable, + bool large_count_representable) noexcept -> neighbor_backend_masks { + constexpr auto legacy = + backend_bit(neighbor_direct_backend::immediate_legacy) | + backend_bit(neighbor_direct_backend::persistent_legacy); + constexpr auto large_count = + backend_bit(neighbor_direct_backend::immediate_large_count) | + backend_bit(neighbor_direct_backend::persistent_large_count); + auto allowed = available & policy_backend_mask(persistence); + auto physical = available; + if (!policy_legacy_representable) { + allowed &= ~legacy; + } + if (!physical_legacy_representable) { + physical &= ~legacy; + } + if (!large_count_representable) { + physical &= ~large_count; + } + allowed &= physical; + return neighbor_backend_masks{ + .allowed = allowed, + .physical = physical, + }; +} + +[[nodiscard]] constexpr auto make_local_backend_masks( + persistence_policy persistence, + bool force_mpi3, + bool policy_legacy_representable, + bool physical_legacy_representable, + bool large_count_representable) noexcept -> neighbor_backend_masks { + return filter_local_backend_masks(compiled_backend_mask(force_mpi3), + persistence, policy_legacy_representable, + physical_legacy_representable, + large_count_representable); +} + +template +[[nodiscard]] auto prepare_direct_neighbor_storage( + pending_neighbor_sends pending_sends, + distributed_graph const& graph, + collective_options collective, + persistence_policy persistence) + -> std::unique_ptr> { + auto semantic_failure = std::string_view{}; + auto result = std::unique_ptr>{}; + { + auto operation_communicator = communicator{graph.view()}; + auto const collective_communicator = operation_communicator.view(); + try { + auto const layout_is_valid = collective_predicate( + pending_sends.locally_valid(graph.destinations().size()), + collective_communicator); + auto const mpi3_ceiling = + validate_collective_options(collective, collective_communicator); + auto const agreed_persistence = + validate_persistence_policy(persistence, collective_communicator); + if (!layout_is_valid) { + semantic_failure = pending_sends.is_fixed() + ? "fixed neighborhood send layout validation " + "failed" + : "direct neighborhood exchange input " + "validation failed"; + } else if (!mpi3_ceiling.has_value() || !agreed_persistence.has_value()) { + semantic_failure = + "direct neighborhood exchange options must agree collectively"; + } else { + auto const compiled_eligible = + compiled_backend_mask(collective.force_mpi3) & + policy_backend_mask(*agreed_persistence); + if (compiled_eligible == 0) { + semantic_failure = + *agreed_persistence == persistence_policy::required + ? "persistent neighborhood exchange is unavailable" + : "direct neighborhood exchange is unavailable"; + } + } + + if (semantic_failure.empty()) { + pending_sends.prepare_fixed_layout(); + auto receive_count_exchange = exchange_neighbor_counts( + pending_sends.counts(), graph.sources().size(), + collective_communicator); + auto receive_layout = + canonical_neighbor_layout(receive_count_exchange.counts); + auto local_capacity = combine_capacity_results( + pending_sends.capacity, + combine_capacity_results(receive_count_exchange.capacity, + receive_layout.capacity)); + if (receive_layout.element_count > + std::numeric_limits::max() / sizeof(T)) { + local_capacity = with_fatal_capacity_issue( + local_capacity, capacity_issue::storage_byte_size_overflow); + } + + auto const policy_legacy_representable = + neighbor_mpi3_layout_is_representable_locally( + pending_sends.counts(), pending_sends.offsets(), + receive_count_exchange.counts, receive_layout.offsets, + *mpi3_ceiling); + auto const physical_legacy_representable = + neighbor_mpi3_layout_is_representable_locally( + pending_sends.counts(), pending_sends.offsets(), + receive_count_exchange.counts, receive_layout.offsets, + static_cast(std::numeric_limits::max())); + auto const large_count_representable = + neighbor_mpi4_layout_is_representable_locally( + pending_sends.counts(), pending_sends.offsets(), + receive_count_exchange.counts, receive_layout.offsets); + auto const common_backends = agree_neighbor_backend_masks( + make_local_backend_masks(*agreed_persistence, collective.force_mpi3, + policy_legacy_representable, + physical_legacy_representable, + large_count_representable), + collective_communicator); + if (common_backends.allowed == 0) { + local_capacity = with_bounded_capacity_issue( + local_capacity, capacity_issue::direct_backend_not_representable); + } + + auto const route = resolve_capacity_collectively( + local_capacity, collective_communicator.native_handle(), + collective_communicator.native_handle(), + "direct neighborhood exchange"); + if (route == capacity_route::bounded) { + if (*agreed_persistence == persistence_policy::required) { + semantic_failure = + "persistent neighborhood exchange requires a single " + "representable payload"; + } else { + auto bounded_plan = make_mpi3_bounded_neighbor_plan( + pending_sends.counts(), pending_sends.offsets(), + receive_count_exchange.counts, receive_layout.offsets, + *mpi3_ceiling, graph, collective_communicator); + auto sends = pending_sends.materialize(); + auto received = segmented_buffer::uninitialized( + receive_layout.element_count, + std::move(receive_count_exchange.counts), + std::move(receive_layout.offsets)); + auto operation_datatype = + make_mpi_datatype(collective_communicator.native_handle()); + result = std::make_unique>( + std::move(operation_communicator), + std::move(operation_datatype), std::move(sends), + std::move(received), direct_neighbor_layout{std::monostate{}}, + neighbor_direct_backend::bounded_legacy, + std::move(bounded_plan)); + } + } else if (auto const backend = + choose_direct_backend(common_backends.allowed); + backend.has_value()) { + auto mpi_layout = build_direct_neighbor_layout( + *backend, pending_sends.counts(), pending_sends.offsets(), + receive_count_exchange.counts, receive_layout.offsets); + auto sends = pending_sends.materialize(); + auto received = segmented_buffer::uninitialized( + receive_layout.element_count, + std::move(receive_count_exchange.counts), + std::move(receive_layout.offsets)); + auto operation_datatype = + make_mpi_datatype(collective_communicator.native_handle()); + result = std::make_unique>( + std::move(operation_communicator), std::move(operation_datatype), + std::move(sends), std::move(received), std::move(mpi_layout), + *backend); + } else { + abort_on_programming_error( + collective_communicator.native_handle(), + "direct neighborhood backend agreement selected no backend"); + } + } + } catch (...) { + abort_on_exception(collective_communicator.native_handle(), + "direct neighborhood exchange local failure"); + } + } + // KAHIP_SEMANTIC_EXIT_BEGIN(async-direct) + if (!semantic_failure.empty()) { + throw_collectively_agreed_semantic_error(graph.native_handle(), + semantic_failure); + } + // KAHIP_SEMANTIC_EXIT_END(async-direct) + return result; +} +} // namespace detail + +template +class neighbor_exchange_request { + public: + neighbor_exchange_request(neighbor_exchange_request const&) = delete; + auto operator=(neighbor_exchange_request const&) + -> neighbor_exchange_request& = delete; + neighbor_exchange_request(neighbor_exchange_request&&) noexcept = default; + auto operator=(neighbor_exchange_request&&) + -> neighbor_exchange_request& = delete; + + ~neighbor_exchange_request() noexcept { + if (state_ != nullptr) { + state_->complete_active_for_destruction(); + } + } + + [[nodiscard]] auto test() noexcept -> bool { + require_state("one-shot neighborhood test on moved-from request"); + state_->require_active_runtime("one-shot neighborhood test"); + if (!state_->active_) { + return true; + } + return state_->test_generation(); + } + + [[nodiscard]] auto wait() && -> segmented_buffer { + require_state("one-shot neighborhood wait on moved-from request"); + state_->require_active_runtime("one-shot neighborhood wait"); + if (state_->active_) { + state_->wait_generation(); + } + auto result = std::move(state_->received_); + state_.reset(); + return result; + } + + private: + explicit neighbor_exchange_request( + std::unique_ptr> state) noexcept + : state_(std::move(state)) {} + + void require_state(std::string_view context) const noexcept { + if (state_ == nullptr) { + abort_on_programming_error(MPI_COMM_WORLD, context); + } + } + + std::unique_ptr> state_; + + template + friend auto start_neighbor_all_to_all_v(segmented_buffer, + distributed_graph const&, + collective_options) + -> neighbor_exchange_request; +}; + +template +[[nodiscard]] auto start_neighbor_all_to_all_v(segmented_buffer sends, + distributed_graph const& graph, + collective_options options = {}) + -> neighbor_exchange_request { + auto state = detail::prepare_direct_neighbor_storage( + detail::pending_neighbor_sends::one_shot(std::move(sends)), graph, + options, persistence_policy::disabled); + state->initiate_immediate(); + return neighbor_exchange_request{std::move(state)}; +} + +template +class neighbor_all_to_all_v_context { + public: + neighbor_all_to_all_v_context(distributed_graph const& graph, + std::vector send_counts, + context_options options = {}) + : state_(detail::prepare_direct_neighbor_storage( + detail::pending_neighbor_sends::fixed(std::move(send_counts)), + graph, + options.collective, + options.persistence)) { + if (detail::is_persistent(state_->backend_)) { + state_->initialize_persistent(); + } + } + + neighbor_all_to_all_v_context(neighbor_all_to_all_v_context const&) = delete; + auto operator=(neighbor_all_to_all_v_context const&) + -> neighbor_all_to_all_v_context& = delete; + neighbor_all_to_all_v_context(neighbor_all_to_all_v_context&&) = delete; + auto operator=(neighbor_all_to_all_v_context&&) + -> neighbor_all_to_all_v_context& = delete; + + ~neighbor_all_to_all_v_context() noexcept { + state_->complete_active_for_destruction(); + state_->release_persistent_request(); + } + + [[nodiscard]] auto send_segment(std::size_t index) noexcept -> std::span { + return state_->send_segment(index); + } + + void start() noexcept { state_->start_generation(); } + + [[nodiscard]] auto test() noexcept -> bool { + return state_->test_generation(); + } + + void wait() noexcept { state_->wait_generation(); } + + [[nodiscard]] auto received_segment(std::size_t index) const noexcept + -> std::span { + return state_->received_segment(index); + } + + private: + std::unique_ptr> state_; +}; +} // namespace parhip::mpi diff --git a/parallel/parallel_src/lib/communication/mpi_collectives.h b/parallel/parallel_src/lib/communication/mpi_collectives.h new file mode 100644 index 00000000..1c42e301 --- /dev/null +++ b/parallel/parallel_src/lib/communication/mpi_collectives.h @@ -0,0 +1,519 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_error.h" +#include "communication/mpi_handles.h" +#include "communication/mpi_types.h" +#include "communication/segmented_buffer.h" +#include "kahip_mpi_capabilities.h" + +namespace parhip::mpi { +namespace capabilities { +inline constexpr bool has_alltoallv_c = KAHIP_HAVE_MPI_ALLTOALLV_C != 0; +inline constexpr bool has_neighbor_alltoallv_c = + KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C != 0; +inline constexpr bool has_ineighbor_alltoallv = + KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV != 0; +inline constexpr bool has_ineighbor_alltoallv_c = + KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV_C != 0; +inline constexpr bool has_neighbor_alltoallv_init = + KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT != 0; +inline constexpr bool has_neighbor_alltoallv_init_c = + KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT_C != 0; +} // namespace capabilities + +struct collective_options { + std::size_t mpi3_round_ceiling = + static_cast(std::numeric_limits::max()); + bool force_mpi3 = false; +}; + +namespace detail { +struct dense_phase_pair final { + std::size_t destination; + std::size_t source; +}; + +// Return (rank + phase) mod size and (rank - phase) mod size without ever +// forming an intermediate larger than size. MPI communicator ranks are ints, +// but doing the arithmetic in int can overflow for valid large communicators. +[[nodiscard]] constexpr auto dense_phase_peers( + std::size_t rank, + std::size_t size, + std::size_t phase) noexcept -> dense_phase_pair { + auto const distance_to_wrap = size - rank; + return dense_phase_pair{ + .destination = + phase >= distance_to_wrap ? phase - distance_to_wrap : rank + phase, + .source = rank >= phase ? rank - phase : size - (phase - rank), + }; +} + +inline auto collective_predicate(bool local_is_valid, + communicator_view communicator) noexcept + -> bool { + int local_valid = local_is_valid ? 1 : 0; + int all_valid = 0; + check_or_abort(MPI_Allreduce(&local_valid, &all_valid, 1, MPI_INT, MPI_MIN, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(collective validation)"); + return all_valid != 0; +} + +inline auto validate_collective_options(collective_options options, + communicator_view communicator) + -> std::optional { + static_assert(sizeof(std::size_t) <= sizeof(std::uint64_t)); + auto const local = std::array{ + static_cast(options.mpi3_round_ceiling), + options.force_mpi3 ? std::uint64_t{1} : std::uint64_t{0}}; + std::array minimum{}; + std::array maximum{}; + check_or_abort(MPI_Allreduce(local.data(), minimum.data(), 2, MPI_UINT64_T, + MPI_MIN, communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(dense collective option minimum)"); + check_or_abort(MPI_Allreduce(local.data(), maximum.data(), 2, MPI_UINT64_T, + MPI_MAX, communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(dense collective option maximum)"); + if (minimum != maximum || options.mpi3_round_ceiling == 0) { + return std::nullopt; + } + return std::min(options.mpi3_round_ceiling, + static_cast(std::numeric_limits::max())); +} + +struct dense_count_exchange final { + std::vector counts; + capacity_result capacity; +}; + +inline auto exchange_counts(std::vector const& send_counts, + communicator_view communicator) + -> dense_count_exchange { + static_assert(sizeof(std::size_t) <= sizeof(std::uint64_t)); + std::vector send(send_counts.begin(), send_counts.end()); + std::vector receive(send_counts.size()); + check_or_abort(MPI_Alltoall(send.data(), 1, MPI_UINT64_T, receive.data(), 1, + MPI_UINT64_T, communicator.native_handle()), + communicator.native_handle(), + "MPI_Alltoall(exchange dense counts)"); + + auto result = std::vector(receive.size()); + auto capacity = capacity_result{}; + for (std::size_t index = 0; index < receive.size(); ++index) { + if (!std::in_range(receive[index])) { + capacity = with_fatal_capacity_issue( + capacity, capacity_issue::received_count_not_representable); + continue; + } + result[index] = static_cast(receive[index]); + } + return dense_count_exchange{ + .counts = std::move(result), + .capacity = capacity, + }; +} + +struct dense_receive_layout final { + std::vector offsets; + std::size_t element_count; + capacity_result capacity; +}; + +inline auto canonical_offsets(std::vector const& counts) + -> dense_receive_layout { + std::vector offsets(counts.size()); + auto capacity = capacity_result{}; + std::size_t total = 0; + auto remains_representable = true; + for (std::size_t index = 0; index < counts.size(); ++index) { + if (!remains_representable) { + continue; + } + offsets[index] = total; + if (counts[index] > std::numeric_limits::max() - total) { + capacity = with_fatal_capacity_issue( + capacity, capacity_issue::cumulative_offset_overflow); + remains_representable = false; + continue; + } + total += counts[index]; + } + return dense_receive_layout{ + .offsets = std::move(offsets), + .element_count = total, + .capacity = capacity, + }; +} + +[[nodiscard]] constexpr auto combine_capacity_results( + capacity_result left, + capacity_result right) noexcept -> capacity_result { + return capacity_result{ + .fatal_issues = left.fatal_issues | right.fatal_issues, + .bounded_fallback_issues = + left.bounded_fallback_issues | right.bounded_fallback_issues, + }; +} + +template +[[nodiscard]] constexpr auto dense_capacity_preflight( + capacity_result local, + std::size_t receive_element_count, + bool mpi4_is_candidate, + bool mpi4_layout_is_representable) noexcept -> capacity_result { + if (receive_element_count > + std::numeric_limits::max() / sizeof(T)) { + local = with_fatal_capacity_issue( + local, capacity_issue::storage_byte_size_overflow); + } + if (mpi4_is_candidate && !mpi4_layout_is_representable) { + local = with_bounded_capacity_issue( + local, capacity_issue::collective_layout_not_representable); + } + return local; +} + +template +[[nodiscard]] auto dense_mpi4_layout_is_representable( + segmented_buffer const& sends, + std::vector const& receive_counts, + std::vector const& receive_offsets) noexcept -> bool { + auto const counts_are_representable = [](std::size_t value) noexcept { + return std::in_range(value); + }; + auto const offsets_are_representable = [](std::size_t value) noexcept { + return std::in_range(value); + }; + return std::ranges::all_of(sends.counts(), counts_are_representable) && + std::ranges::all_of(receive_counts, counts_are_representable) && + std::ranges::all_of(sends.offsets(), offsets_are_representable) && + std::ranges::all_of(receive_offsets, offsets_are_representable); +} + +inline auto checked_int(std::size_t value, std::string_view context) -> int { + if (value > static_cast(std::numeric_limits::max())) { + throw mpi_error{MPI_ERR_COUNT, std::string{context}}; + } + return static_cast(value); +} + +template +inline auto needs_bounded_rounds( + segmented_buffer const& sends, + std::vector const& receive_counts, + std::vector const& receive_offsets, + std::size_t ceiling, + communicator_view communicator) -> bool { + auto local_needs_rounds = false; + for (std::size_t index = 0; index < sends.segment_count(); ++index) { + local_needs_rounds = + local_needs_rounds || sends.counts()[index] > ceiling || + sends.offsets()[index] > ceiling || receive_counts[index] > ceiling || + receive_offsets[index] > ceiling; + } + int local = local_needs_rounds ? 1 : 0; + int global = 0; + check_or_abort(MPI_Allreduce(&local, &global, 1, MPI_INT, MPI_MAX, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(select MPI-3 collective path)"); + return global != 0; +} + +template +void mpi3_bounded_all_to_all_v(segmented_buffer const& sends, + std::span receive_storage, + std::vector const& receive_counts, + std::vector const& receive_offsets, + MPI_Datatype datatype, + std::size_t ceiling, + communicator_view communicator) { + auto const rank = static_cast(communicator.rank()); + auto const size = static_cast(communicator.size()); + std::vector send_counts(size, 0); + std::vector receive_counts_i(size, 0); + std::vector displacements(size, 0); + + for (std::size_t phase = 0; phase < size; ++phase) { + auto const [destination_index, source_index] = + dense_phase_peers(rank, size, phase); + auto const send_total = sends.counts()[destination_index]; + auto const local_rounds = send_total == 0 + ? std::size_t{0} + : (send_total - 1) / ceiling + std::size_t{1}; + auto local_rounds_u64 = static_cast(local_rounds); + std::uint64_t phase_rounds_u64 = 0; + check_or_abort( + MPI_Allreduce(&local_rounds_u64, &phase_rounds_u64, 1, MPI_UINT64_T, + MPI_MAX, communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(MPI-3 bounded phase rounds)"); + auto const phase_rounds = static_cast(phase_rounds_u64); + + for (std::size_t round = 0; round < phase_rounds; ++round) { + std::ranges::fill(send_counts, 0); + std::ranges::fill(receive_counts_i, 0); + auto const chunk_offset = round * ceiling; + auto const send_chunk = chunk_offset < send_total + ? std::min(ceiling, send_total - chunk_offset) + : std::size_t{0}; + auto const receive_total = receive_counts[source_index]; + auto const receive_chunk = + chunk_offset < receive_total + ? std::min(ceiling, receive_total - chunk_offset) + : std::size_t{0}; + send_counts[destination_index] = + checked_int(send_chunk, "MPI-3 bounded send chunk"); + receive_counts_i[source_index] = + checked_int(receive_chunk, "MPI-3 bounded receive chunk"); + + auto const* send_buffer = sends.storage().data(); + if (send_chunk != 0) { + send_buffer += sends.offsets()[destination_index] + chunk_offset; + } + auto* receive_buffer = receive_storage.data(); + if (receive_chunk != 0) { + receive_buffer += receive_offsets[source_index] + chunk_offset; + } + check_or_abort( + MPI_Alltoallv(send_buffer, send_counts.data(), displacements.data(), + datatype, receive_buffer, receive_counts_i.data(), + displacements.data(), datatype, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Alltoallv(MPI-3 bounded dense round)"); + } + } +} + +#if KAHIP_HAVE_MPI_ALLTOALLV_C || KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C || \ + KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV_C || \ + KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT_C +inline auto checked_mpi_count(std::size_t value, std::string_view context) + -> MPI_Count { + if (!std::in_range(value)) { + throw mpi_error{MPI_ERR_COUNT, std::string{context}}; + } + return static_cast(value); +} + +inline auto checked_mpi_aint(std::size_t value, std::string_view context) + -> MPI_Aint { + if (!std::in_range(value)) { + throw mpi_error{MPI_ERR_COUNT, std::string{context}}; + } + return static_cast(value); +} +#endif +} // namespace detail + +inline void validate_collectively(bool local_is_valid, + communicator_view communicator, + std::string_view context) { + auto all_valid = false; + { + auto owned_communicator = parhip::mpi::communicator{communicator}; + all_valid = + detail::collective_predicate(local_is_valid, owned_communicator.view()); + } + // KAHIP_SEMANTIC_EXIT_BEGIN(validate-collectively) + if (!all_valid) { + throw_collectively_agreed_semantic_error(communicator.native_handle(), + context); + } + // KAHIP_SEMANTIC_EXIT_END(validate-collectively) +} + +template +[[nodiscard]] auto agree_collectively(T local_value, + communicator_view communicator, + std::string_view context) -> T { + auto minimum = T{}; + auto maximum = T{}; + { + auto owned_communicator = parhip::mpi::communicator{communicator}; + auto const collective_communicator = owned_communicator.view(); + auto const datatype = get_mpi_datatype(); + check_or_abort(MPI_Allreduce(&local_value, &minimum, 1, datatype, MPI_MIN, + collective_communicator.native_handle()), + collective_communicator.native_handle(), + "MPI_Allreduce(common value minimum)"); + check_or_abort(MPI_Allreduce(&local_value, &maximum, 1, datatype, MPI_MAX, + collective_communicator.native_handle()), + collective_communicator.native_handle(), + "MPI_Allreduce(common value maximum)"); + } + // KAHIP_SEMANTIC_EXIT_BEGIN(agree-collectively) + if (minimum != maximum) { + throw_collectively_agreed_semantic_error(communicator.native_handle(), + context); + } + // KAHIP_SEMANTIC_EXIT_END(agree-collectively) + return minimum; +} + +template +[[nodiscard]] auto all_to_all_v(segmented_buffer sends, + communicator_view communicator, + collective_options options = {}) + -> segmented_buffer { + auto semantic_failure = std::string_view{}; + auto result = std::optional>{}; + { + auto owned_communicator = parhip::mpi::communicator{communicator}; + auto const collective_communicator = owned_communicator.view(); + try { + auto const communicator_size = + static_cast(collective_communicator.size()); + auto const layout_is_valid = detail::collective_predicate( + sends.has_canonical_layout(communicator_size), + collective_communicator); + if (!layout_is_valid) { + semantic_failure = "all_to_all_v collective input validation failed"; + } else { + auto const mpi3_ceiling = detail::validate_collective_options( + options, collective_communicator); + if (!mpi3_ceiling.has_value()) { + semantic_failure = + "all_to_all_v collective options must match and use a " + "nonzero MPI-3 ceiling"; + } else { + auto count_exchange = + detail::exchange_counts(sends.counts(), collective_communicator); + auto receive_layout = + detail::canonical_offsets(count_exchange.counts); + auto local_capacity = detail::combine_capacity_results( + count_exchange.capacity, receive_layout.capacity); + auto const mpi4_is_candidate = + capabilities::has_alltoallv_c && !options.force_mpi3; + auto const mpi4_layout_is_representable = + !mpi4_is_candidate || + detail::dense_mpi4_layout_is_representable( + sends, count_exchange.counts, receive_layout.offsets); + local_capacity = detail::dense_capacity_preflight( + local_capacity, receive_layout.element_count, mpi4_is_candidate, + mpi4_layout_is_representable); + auto const route = resolve_capacity_collectively( + local_capacity, collective_communicator.native_handle(), + collective_communicator.native_handle(), "all_to_all_v"); + auto received = segmented_buffer::uninitialized( + receive_layout.element_count, std::move(count_exchange.counts), + std::move(receive_layout.offsets)); + auto datatype = + make_mpi_datatype(collective_communicator.native_handle()); + auto payload_complete = false; + +#if KAHIP_HAVE_MPI_ALLTOALLV_C + if (route == parhip::mpi::capacity_route::direct && + !options.force_mpi3) { + std::vector send_counts; + std::vector receive_counts_c; + std::vector send_offsets; + std::vector receive_offsets_c; + send_counts.reserve(communicator_size); + receive_counts_c.reserve(communicator_size); + send_offsets.reserve(communicator_size); + receive_offsets_c.reserve(communicator_size); + for (std::size_t index = 0; index < communicator_size; ++index) { + send_counts.push_back(detail::checked_mpi_count( + sends.counts()[index], "MPI send count")); + receive_counts_c.push_back(detail::checked_mpi_count( + received.counts()[index], "MPI receive count")); + send_offsets.push_back(detail::checked_mpi_aint( + sends.offsets()[index], "MPI send offset")); + receive_offsets_c.push_back(detail::checked_mpi_aint( + received.offsets()[index], "MPI receive offset")); + } + check_or_abort( + MPI_Alltoallv_c( + sends.storage().data(), send_counts.data(), + send_offsets.data(), datatype.native_handle(), + received.storage().data(), receive_counts_c.data(), + receive_offsets_c.data(), datatype.native_handle(), + collective_communicator.native_handle()), + collective_communicator.native_handle(), + "MPI_Alltoallv_c(dense exchange)"); + payload_complete = true; + } +#endif + + if (!payload_complete && + route == parhip::mpi::capacity_route::bounded) { + detail::mpi3_bounded_all_to_all_v( + sends, received.storage(), received.counts(), + received.offsets(), datatype.native_handle(), *mpi3_ceiling, + collective_communicator); + payload_complete = true; + } + if (!payload_complete && + detail::needs_bounded_rounds(sends, received.counts(), + received.offsets(), *mpi3_ceiling, + collective_communicator)) { + detail::mpi3_bounded_all_to_all_v( + sends, received.storage(), received.counts(), + received.offsets(), datatype.native_handle(), *mpi3_ceiling, + collective_communicator); + payload_complete = true; + } + if (!payload_complete) { + std::vector send_counts; + std::vector receive_counts_i; + std::vector send_offsets; + std::vector receive_offsets_i; + send_counts.reserve(communicator_size); + receive_counts_i.reserve(communicator_size); + send_offsets.reserve(communicator_size); + receive_offsets_i.reserve(communicator_size); + for (std::size_t index = 0; index < communicator_size; ++index) { + send_counts.push_back( + detail::checked_int(sends.counts()[index], "MPI send count")); + receive_counts_i.push_back(detail::checked_int( + received.counts()[index], "MPI receive count")); + send_offsets.push_back(detail::checked_int(sends.offsets()[index], + "MPI send offset")); + receive_offsets_i.push_back(detail::checked_int( + received.offsets()[index], "MPI receive offset")); + } + check_or_abort( + MPI_Alltoallv(sends.storage().data(), send_counts.data(), + send_offsets.data(), datatype.native_handle(), + received.storage().data(), + receive_counts_i.data(), receive_offsets_i.data(), + datatype.native_handle(), + collective_communicator.native_handle()), + collective_communicator.native_handle(), + "MPI_Alltoallv(dense exchange)"); + } + result.emplace(std::move(received)); + } + } + } catch (...) { + abort_on_exception(collective_communicator.native_handle(), + "all_to_all_v local failure"); + } + } + + // KAHIP_SEMANTIC_EXIT_BEGIN(dense-all-to-all) + if (!semantic_failure.empty()) { + throw_collectively_agreed_semantic_error(communicator.native_handle(), + semantic_failure); + } + // KAHIP_SEMANTIC_EXIT_END(dense-all-to-all) + return std::move(*result); +} +} // namespace parhip::mpi diff --git a/parallel/parallel_src/lib/communication/mpi_error.h b/parallel/parallel_src/lib/communication/mpi_error.h new file mode 100644 index 00000000..3922d247 --- /dev/null +++ b/parallel/parallel_src/lib/communication/mpi_error.h @@ -0,0 +1,47 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +namespace parhip::mpi { +class mpi_error final : public std::runtime_error { +public: + explicit mpi_error( + int error_code, + std::string context, + std::source_location location = std::source_location::current()) + : std::runtime_error(make_message(error_code, context, location)), + error_code_(error_code), + context_(std::move(context)), + location_(location) {} + + [[nodiscard]] auto error_code() const noexcept -> int { return error_code_; } + [[nodiscard]] auto context() const noexcept -> std::string_view { + return context_; + } + [[nodiscard]] auto location() const noexcept -> std::source_location { + return location_; + } + +private: + static auto make_message(int error_code, + std::string_view context, + std::source_location location) -> std::string { + auto message = std::ostringstream{}; + message << context << " at " << location.file_name() << ':' + << location.line() << " (MPI error " << error_code << ')'; + return message.str(); + } + + int error_code_; + std::string context_; + std::source_location location_; +}; + +} // namespace parhip::mpi diff --git a/parallel/parallel_src/lib/communication/mpi_failure.cpp b/parallel/parallel_src/lib/communication/mpi_failure.cpp new file mode 100644 index 00000000..e3355575 --- /dev/null +++ b/parallel/parallel_src/lib/communication/mpi_failure.cpp @@ -0,0 +1,295 @@ +#include "communication/mpi_failure.h" + +#include +#include +#include +#include +#include + +#include "communication/mpi_error.h" +#include "tools/fatal_diagnostics.h" + +namespace parhip::mpi { +namespace { +enum class runtime_state { + before_initialization, + active, + finalized, +}; + +[[nodiscard]] auto active_rank(MPI_Comm communicator) noexcept + -> std::optional { + auto const affected = + communicator == MPI_COMM_NULL ? MPI_COMM_WORLD : communicator; + auto rank = 0; + return MPI_Comm_rank(affected, &rank) == MPI_SUCCESS + ? std::optional{rank} + : std::nullopt; +} + +[[noreturn]] void abort_on_lifecycle_query_failure(std::string_view query, + int error_code) noexcept { + kahip::diagnostics::critical( + "MPI lifecycle query failure: ", query, " returned raw error ", + error_code); + std::abort(); +} + +[[nodiscard]] auto query_runtime_state() noexcept -> runtime_state { + int initialized = 0; + int finalized = 0; + auto const initialized_result = MPI_Initialized(&initialized); + if (initialized_result != MPI_SUCCESS) { + abort_on_lifecycle_query_failure("MPI_Initialized", initialized_result); + } + if (initialized == 0) { + return runtime_state::before_initialization; + } + auto const finalized_result = MPI_Finalized(&finalized); + if (finalized_result != MPI_SUCCESS) { + abort_on_lifecycle_query_failure("MPI_Finalized", finalized_result); + } + return finalized == 0 ? runtime_state::active : runtime_state::finalized; +} + +void log_failure(std::string_view boundary, + std::exception_ptr failure, + std::optional rank) noexcept { + if (failure == nullptr) { + if (rank.has_value()) { + kahip::diagnostics::critical( + boundary, ": unknown unrecoverable failure (rank ", *rank, ")"); + } else { + kahip::diagnostics::critical(boundary, + ": unknown unrecoverable failure"); + } + return; + } + try { + std::rethrow_exception(failure); + } catch (std::exception const& error) { + if (rank.has_value()) { + kahip::diagnostics::critical(boundary, ": ", error.what(), " (rank ", + *rank, ")"); + } else { + kahip::diagnostics::critical(boundary, ": ", error.what()); + } + } catch (...) { + if (rank.has_value()) { + kahip::diagnostics::critical( + boundary, ": unknown unrecoverable exception (rank ", *rank, ")"); + } else { + kahip::diagnostics::critical(boundary, + ": unknown unrecoverable exception"); + } + } +} + +void log_raw_mpi_failure(int error_code, + std::string_view context, + std::source_location location) noexcept { + kahip::diagnostics::critical( + "MPI backend failure: ", context, " at ", location.file_name(), ":", + location.line(), " (original raw code ", error_code, ")"); +} + +void log_active_mpi_failure(int error_code, + std::string_view context, + std::source_location location, + std::optional world_rank) noexcept { + auto error_text = std::array{}; + auto error_text_length = 0; + auto const formatter_result = + MPI_Error_string(error_code, error_text.data(), &error_text_length); + if (formatter_result != MPI_SUCCESS) { + kahip::diagnostics::critical( + "MPI backend failure: ", context, " at ", location.file_name(), ":", + location.line(), " (original raw code ", error_code, + ", MPI_Error_string secondary raw code ", formatter_result, + ", world rank ", world_rank.value_or(-1), ")"); + return; + } + if (error_text_length < 0 || + static_cast(error_text_length) > error_text.size()) { + kahip::diagnostics::critical( + "MPI backend failure: ", context, " at ", location.file_name(), ":", + location.line(), " (original raw code ", error_code, + ", MPI_Error_string invalid length ", error_text_length, + ", world rank ", world_rank.value_or(-1), ")"); + return; + } + kahip::diagnostics::critical( + "MPI backend failure: ", context, " at ", location.file_name(), ":", + location.line(), " (original raw code ", error_code, ", MPI text: ", + std::string_view{error_text.data(), + static_cast(error_text_length)}, + ", world rank ", world_rank.value_or(-1), ")"); +} +} // namespace + +auto runtime_is_active() noexcept -> bool { + return query_runtime_state() == runtime_state::active; +} + +[[noreturn]] void abort_on_exception(MPI_Comm communicator, + std::string_view boundary, + std::exception_ptr failure) noexcept { + auto const active = runtime_is_active(); + log_failure(boundary, failure, + active ? active_rank(communicator) : std::nullopt); + if (active) { + auto const affected = + communicator == MPI_COMM_NULL ? MPI_COMM_WORLD : communicator; + MPI_Abort(affected, EXIT_FAILURE); + } + std::abort(); +} + +[[noreturn]] void abort_on_mpi_error(MPI_Comm communicator, + int error_code, + std::string_view context, + std::source_location location) noexcept { + auto const mpi_is_active = runtime_is_active(); + if (mpi_is_active) { + log_active_mpi_failure(error_code, context, location, + active_rank(MPI_COMM_WORLD)); + } else { + // MPI_Error_string is itself an MPI call and is not valid before + // initialization or after finalization. Retain the raw code instead. + log_raw_mpi_failure(error_code, context, location); + } + + if (mpi_is_active) { + auto const affected = + communicator == MPI_COMM_NULL ? MPI_COMM_WORLD : communicator; + MPI_Abort(affected, EXIT_FAILURE); + } + std::abort(); +} + +[[noreturn]] void abort_on_backend_failure(MPI_Comm communicator, + std::string_view context) noexcept { + auto const active = runtime_is_active(); + if (auto const rank = active ? active_rank(communicator) : std::nullopt; + rank.has_value()) { + kahip::diagnostics::critical( + "Distributed backend failure: ", context, " (rank ", *rank, ")"); + } else { + kahip::diagnostics::critical("Distributed backend failure: ", context); + } + if (active) { + auto const affected = + communicator == MPI_COMM_NULL ? MPI_COMM_WORLD : communicator; + MPI_Abort(affected, EXIT_FAILURE); + } + std::abort(); +} + +[[noreturn]] void throw_collectively_agreed_semantic_error( + MPI_Comm communicator, + std::string_view context, + std::source_location location) { + detail::throw_collectively_agreed_semantic_error_from( + communicator, [context, location]() -> mpi_error { + return mpi_error{MPI_ERR_ARG, std::string{context}, location}; + }); +} + +[[noreturn]] void abort_on_programming_error( + MPI_Comm communicator, + std::string_view context) noexcept { + auto const active = runtime_is_active(); + if (auto const rank = active ? active_rank(communicator) : std::nullopt; + rank.has_value()) { + kahip::diagnostics::critical( + "MPI adapter programming failure: ", context, " (rank ", *rank, + ")"); + } else { + kahip::diagnostics::critical("MPI adapter programming failure: ", + context); + } + if (active) { + auto const affected = + communicator == MPI_COMM_NULL ? MPI_COMM_WORLD : communicator; + MPI_Abort(affected, EXIT_FAILURE); + } + std::abort(); +} + +[[noreturn]] void abort_on_capacity_failure( + MPI_Comm communicator, + std::string_view boundary, + std::string_view issue_diagnostic) noexcept { + auto const active = runtime_is_active(); + if (auto const rank = active ? active_rank(communicator) : std::nullopt; + rank.has_value()) { + kahip::diagnostics::critical( + "MPI adapter capacity failure: ", boundary, ": ", issue_diagnostic, + " (rank ", *rank, ")"); + } else { + kahip::diagnostics::critical( + "MPI adapter capacity failure: ", boundary, ": ", issue_diagnostic); + } + if (active) { + auto const affected = + communicator == MPI_COMM_NULL ? MPI_COMM_WORLD : communicator; + MPI_Abort(affected, EXIT_FAILURE); + } + std::abort(); +} + +auto resolve_capacity_collectively(capacity_result local, + MPI_Comm convergence_communicator, + MPI_Comm abort_communicator, + std::string_view boundary) noexcept + -> capacity_route { + if (!runtime_is_active()) { + abort_on_inactive_mpi_ownership("capacity resolution"); + } + if (convergence_communicator == MPI_COMM_NULL) { + abort_on_programming_error( + abort_communicator, + "capacity resolution requires a live convergence intracommunicator"); + } + + auto is_intercommunicator = 0; + check_or_abort( + MPI_Comm_test_inter(convergence_communicator, &is_intercommunicator), + convergence_communicator, "MPI_Comm_test_inter(capacity resolution)"); + if (is_intercommunicator != 0) { + abort_on_programming_error( + abort_communicator, + "capacity resolution requires a live convergence intracommunicator"); + } + + auto const local_masks = std::array{ + local.fatal_issues, + local.bounded_fallback_issues, + }; + auto global_masks = std::array{}; + check_or_abort(MPI_Allreduce(local_masks.data(), global_masks.data(), + static_cast(global_masks.size()), + MPI_UINT64_T, MPI_BOR, convergence_communicator), + convergence_communicator, + "MPI_Allreduce(capacity resolution)"); + + auto const global = capacity_result{ + .fatal_issues = global_masks[0], + .bounded_fallback_issues = global_masks[1], + }; + if (auto const fatal_issue = first_fatal_capacity_issue(global); + fatal_issue.has_value()) { + abort_on_capacity_failure(abort_communicator, boundary, + capacity_issue_diagnostic(*fatal_issue)); + } + return global.bounded_fallback_issues != 0 ? capacity_route::bounded + : capacity_route::direct; +} + +[[noreturn]] void abort_on_inactive_mpi_ownership( + std::string_view context) noexcept { + kahip::diagnostics::critical( + "MPI adapter ownership outlived the active MPI runtime: ", context); + std::abort(); +} +} // namespace parhip::mpi diff --git a/parallel/parallel_src/lib/communication/mpi_failure.h b/parallel/parallel_src/lib/communication/mpi_failure.h new file mode 100644 index 00000000..252f754d --- /dev/null +++ b/parallel/parallel_src/lib/communication/mpi_failure.h @@ -0,0 +1,213 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_error.h" + +namespace parhip::mpi { +enum class capacity_issue : std::uint64_t { + received_count_not_representable = std::uint64_t{1} << 0, + cumulative_offset_overflow = std::uint64_t{1} << 1, + storage_byte_size_overflow = std::uint64_t{1} << 2, + topology_degree_not_representable = std::uint64_t{1} << 3, + collective_layout_not_representable = std::uint64_t{1} << 4, + direct_backend_not_representable = std::uint64_t{1} << 5, + bounded_round_arithmetic_overflow = std::uint64_t{1} << 6, +}; + +enum class capacity_route : std::uint8_t { + direct, + bounded, +}; + +struct capacity_result final { + std::uint64_t fatal_issues = 0; + std::uint64_t bounded_fallback_issues = 0; + + auto operator==(capacity_result const&) const -> bool = default; +}; + +static_assert(std::is_trivially_copyable_v); +static_assert(std::is_standard_layout_v); + +[[nodiscard]] constexpr auto capacity_issue_mask(capacity_issue issue) noexcept + -> std::uint64_t { + return static_cast(issue); +} + +[[nodiscard]] constexpr auto capacity_issue_diagnostic( + capacity_issue issue) noexcept -> std::string_view { + switch (issue) { + case capacity_issue::received_count_not_representable: + return "received element count exceeds local size_t capacity"; + case capacity_issue::cumulative_offset_overflow: + return "cumulative element offset exceeds local size_t capacity"; + case capacity_issue::storage_byte_size_overflow: + return "element storage byte size exceeds local size_t capacity"; + case capacity_issue::topology_degree_not_representable: + return "distributed graph outdegree exceeds MPI int capacity"; + case capacity_issue::collective_layout_not_representable: + return "collective payload layout has no representable MPI backend"; + case capacity_issue::direct_backend_not_representable: + return "direct neighborhood payload has no representable MPI backend"; + case capacity_issue::bounded_round_arithmetic_overflow: + return "bounded MPI-3 chunk arithmetic exceeds local size_t capacity"; + } + return "unknown capacity issue"; +} + +[[nodiscard]] constexpr auto with_fatal_capacity_issue( + capacity_result result, + capacity_issue issue) noexcept -> capacity_result { + result.fatal_issues |= capacity_issue_mask(issue); + return result; +} + +[[nodiscard]] constexpr auto with_bounded_capacity_issue( + capacity_result result, + capacity_issue issue) noexcept -> capacity_result { + result.bounded_fallback_issues |= capacity_issue_mask(issue); + return result; +} + +[[nodiscard]] constexpr auto has_fatal_capacity_issue( + capacity_result result, + capacity_issue issue) noexcept -> bool { + return (result.fatal_issues & capacity_issue_mask(issue)) != 0; +} + +[[nodiscard]] constexpr auto has_bounded_capacity_issue( + capacity_result result, + capacity_issue issue) noexcept -> bool { + return (result.bounded_fallback_issues & capacity_issue_mask(issue)) != 0; +} + +[[nodiscard]] constexpr auto first_fatal_capacity_issue( + capacity_result result) noexcept -> std::optional { + if (result.fatal_issues == 0) { + return std::nullopt; + } + auto const lowest_issue = + result.fatal_issues & (~result.fatal_issues + std::uint64_t{1}); + return static_cast(lowest_issue); +} + +[[nodiscard]] constexpr auto capacity_route_for(capacity_result result) noexcept + -> std::optional { + if (result.fatal_issues != 0) { + return std::nullopt; + } + return result.bounded_fallback_issues != 0 ? capacity_route::bounded + : capacity_route::direct; +} + +[[nodiscard]] auto runtime_is_active() noexcept -> bool; + +template +void run_with_exception_barrier(Operation&& operation, + OnFailure&& on_failure) noexcept { + try { + std::invoke(std::forward(operation)); + } catch (...) { + std::invoke(std::forward(on_failure), std::current_exception()); + } +} + +[[noreturn]] void abort_on_exception( + MPI_Comm communicator, + std::string_view boundary, + std::exception_ptr failure = std::current_exception()) noexcept; + +[[noreturn]] void abort_on_mpi_error( + MPI_Comm communicator, + int error_code, + std::string_view context, + std::source_location location = std::source_location::current()) noexcept; + +[[noreturn]] void abort_on_backend_failure(MPI_Comm communicator, + std::string_view context) noexcept; + +[[noreturn]] void abort_on_programming_error(MPI_Comm communicator, + std::string_view context) noexcept; + +[[noreturn]] void abort_on_capacity_failure( + MPI_Comm communicator, + std::string_view boundary, + std::string_view issue_diagnostic) noexcept; + +[[noreturn]] void abort_on_inactive_mpi_ownership( + std::string_view context) noexcept; + +namespace detail { +template +concept semantic_error_factory = + std::invocable && + std::same_as, mpi_error>; + +template +[[noreturn]] void throw_collectively_agreed_semantic_error_from( + MPI_Comm communicator, + Factory&& factory) { + auto structured_error = std::exception_ptr{}; + try { + structured_error = + std::make_exception_ptr(std::invoke(std::forward(factory))); + if (structured_error == nullptr) { + abort_on_exception(communicator, "MPI semantic error construction", {}); + } + try { + std::rethrow_exception(structured_error); + } catch (mpi_error const&) { + // make_exception_ptr is noexcept and may store a copy/allocation failure + // instead of the requested type. Only the intended structured error may + // leave this construction barrier. + } catch (...) { + abort_on_exception(communicator, "MPI semantic error construction", + std::current_exception()); + } + } catch (...) { + abort_on_exception(communicator, "MPI semantic error construction", + std::current_exception()); + } + std::rethrow_exception(structured_error); +} +} // namespace detail + +// Precondition: every rank in communicator has already reached the same +// invalid semantic decision and no payload or externally visible state has +// been mutated. This helper performs no hidden collective. +[[noreturn]] void throw_collectively_agreed_semantic_error( + MPI_Comm communicator, + std::string_view context, + std::source_location location = std::source_location::current()); + +// convergence_communicator must be a live intracommunicator. Invalid runtime, +// null, or intercommunicator use terminates before the payload-free reduction. +// Every valid caller performs exactly one two-mask MPI_BOR reduction. +[[nodiscard]] auto resolve_capacity_collectively( + capacity_result local, + MPI_Comm convergence_communicator, + MPI_Comm abort_communicator, + std::string_view boundary) noexcept -> capacity_route; + +inline void check_or_abort( + int error_code, + MPI_Comm communicator, + std::string_view context, + std::source_location location = std::source_location::current()) noexcept { + if (error_code != MPI_SUCCESS) { + abort_on_mpi_error(communicator, error_code, context, location); + } +} +} // namespace parhip::mpi diff --git a/parallel/parallel_src/lib/communication/mpi_fixed_broadcast.h b/parallel/parallel_src/lib/communication/mpi_fixed_broadcast.h new file mode 100644 index 00000000..a8dc988f --- /dev/null +++ b/parallel/parallel_src/lib/communication/mpi_fixed_broadcast.h @@ -0,0 +1,138 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_failure.h" +#include "communication/mpi_handles.h" +#include "communication/mpi_types.h" + +namespace parhip::mpi { +struct broadcast_options final { + std::size_t mpi3_round_ceiling = + static_cast(std::numeric_limits::max()); + bool force_mpi3 = false; +}; + +namespace detail { +inline void validate_broadcast_parameters(std::size_t count, + int root, + communicator_view communicator, + broadcast_options options) noexcept { + static_assert(sizeof(std::size_t) <= sizeof(std::uint64_t)); + auto const encoded_root = root >= 0 + ? static_cast(root) + : std::numeric_limits::max(); + auto const local = std::array{ + static_cast(count), + static_cast(options.mpi3_round_ceiling), + options.force_mpi3 ? std::uint64_t{1} : std::uint64_t{0}, encoded_root}; + auto minimum = std::array{}; + auto maximum = std::array{}; + check_or_abort(MPI_Allreduce(local.data(), minimum.data(), + static_cast(local.size()), MPI_UINT64_T, + MPI_MIN, communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(bounded broadcast signature minimum)"); + check_or_abort(MPI_Allreduce(local.data(), maximum.data(), + static_cast(local.size()), MPI_UINT64_T, + MPI_MAX, communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(bounded broadcast signature maximum)"); + + if (minimum != maximum) { + abort_on_programming_error( + communicator.native_handle(), + "bounded broadcast arguments differ across communicator"); + } + if (options.mpi3_round_ceiling == 0 || root < 0 || + root >= communicator.size()) { + abort_on_programming_error(communicator.native_handle(), + "bounded broadcast arguments are invalid"); + } +} +} // namespace detail + +template + requires(Extent != std::dynamic_extent && + Extent <= static_cast(std::numeric_limits::max())) +void broadcast_fixed(std::span values, + int root, + communicator_view communicator, + std::string_view context) noexcept { + check_or_abort( + MPI_Bcast(values.data(), static_cast(Extent), get_mpi_datatype(), + root, communicator.native_handle()), + communicator.native_handle(), context); +} + +template +void broadcast_fixed(T& value, + int root, + communicator_view communicator, + std::string_view context) noexcept { + broadcast_fixed(std::span{&value, 1}, root, communicator, context); +} + +template +void broadcast_bounded(std::span values, + int root, + communicator_view communicator, + std::string_view context, + broadcast_options options = {}) noexcept { + require_live_intracommunicator( + communicator, "bounded broadcast requires a live intracommunicator"); + detail::validate_broadcast_parameters(values.size(), root, communicator, + options); +#if KAHIP_HAVE_MPI_BCAST_C + if (!options.force_mpi3 && std::in_range(values.size())) { + check_or_abort( + MPI_Bcast_c(values.data(), static_cast(values.size()), + get_mpi_datatype(), root, communicator.native_handle()), + communicator.native_handle(), context); + return; + } +#endif + + auto const ceiling = + std::min(options.mpi3_round_ceiling, + static_cast(std::numeric_limits::max())); + if (values.empty()) { + check_or_abort(MPI_Bcast(values.data(), 0, get_mpi_datatype(), root, + communicator.native_handle()), + communicator.native_handle(), context); + return; + } + for (std::size_t offset = 0; offset < values.size();) { + auto const chunk = std::min(ceiling, values.size() - offset); + check_or_abort( + MPI_Bcast(values.data() + offset, static_cast(chunk), + get_mpi_datatype(), root, communicator.native_handle()), + communicator.native_handle(), context); + offset += chunk; + } +} + +template +void broadcast_vcycle_state(std::span partition_map, + EdgeWeight& previous_cut, + NodeWeight& previous_maximum_block_weight, + int root, + communicator_view communicator, + broadcast_options options = {}) noexcept { + broadcast_bounded(partition_map, root, communicator, + "MPI_Bcast(previous partition map)", options); + broadcast_fixed(previous_cut, root, communicator, + "MPI_Bcast(previous edge cut)"); + broadcast_fixed(previous_maximum_block_weight, root, communicator, + "MPI_Bcast(previous maximum block weight)"); +} +} // namespace parhip::mpi diff --git a/parallel/parallel_src/lib/communication/mpi_fixed_reduction.h b/parallel/parallel_src/lib/communication/mpi_fixed_reduction.h new file mode 100644 index 00000000..ce9f8356 --- /dev/null +++ b/parallel/parallel_src/lib/communication/mpi_fixed_reduction.h @@ -0,0 +1,322 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_failure.h" +#include "communication/mpi_handles.h" +#include "communication/mpi_types.h" +#include "kahip_mpi_capabilities.h" + +namespace parhip::mpi { +enum class reduction_kind : std::uint8_t { + sum, + minimum, + maximum, +}; + +struct reduction_options final { + std::size_t mpi3_round_ceiling = + static_cast(std::numeric_limits::max()); + bool force_mpi3 = false; +}; + +template +concept mpi_integral_reduction_datatype = + mpi_native_datatype && + std::integral>> && + (!std::is_same_v>, bool>); + +namespace detail { +[[nodiscard]] constexpr auto reduction_operation(reduction_kind kind) noexcept + -> MPI_Op { + switch (kind) { + case reduction_kind::sum: + return MPI_SUM; + case reduction_kind::minimum: + return MPI_MIN; + case reduction_kind::maximum: + return MPI_MAX; + } + return MPI_OP_NULL; +} + +inline void validate_reduction_parameters(std::size_t send_count, + std::size_t receive_count, + bool buffers_are_distinct, + reduction_kind kind, + std::optional root, + communicator_view communicator, + reduction_options options) noexcept { + static_assert(sizeof(std::size_t) <= sizeof(std::uint64_t)); + constexpr auto all_reduce_root = std::numeric_limits::max(); + constexpr auto invalid_reduce_root = all_reduce_root - 1; + auto const encoded_root = !root.has_value() ? all_reduce_root + : *root >= 0 ? static_cast(*root) + : invalid_reduce_root; + auto const local = std::array{ + static_cast(send_count), + static_cast(receive_count), + static_cast(options.mpi3_round_ceiling), + options.force_mpi3 ? std::uint64_t{1} : std::uint64_t{0}, + static_cast(kind), + encoded_root, + buffers_are_distinct ? std::uint64_t{1} : std::uint64_t{0}}; + auto minimum = std::array{}; + auto maximum = std::array{}; + check_or_abort(MPI_Allreduce(local.data(), minimum.data(), + static_cast(local.size()), MPI_UINT64_T, + MPI_MIN, communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(bounded reduction signature minimum)"); + check_or_abort(MPI_Allreduce(local.data(), maximum.data(), + static_cast(local.size()), MPI_UINT64_T, + MPI_MAX, communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(bounded reduction signature maximum)"); + + if (minimum != maximum) { + abort_on_programming_error( + communicator.native_handle(), + "bounded reduction arguments differ across communicator"); + } + auto const encoded_kind = static_cast(kind); + auto const root_is_valid = + !root.has_value() || (*root >= 0 && *root < communicator.size()); + if (send_count != receive_count || !buffers_are_distinct || + options.mpi3_round_ceiling == 0 || + encoded_kind > static_cast(reduction_kind::maximum) || + !root_is_valid) { + abort_on_programming_error(communicator.native_handle(), + "bounded reduction arguments are invalid"); + } +} + +template +void reduce_bounded_impl(std::span local_values, + std::span reduced_values, + reduction_kind kind, + std::optional root, + communicator_view communicator, + std::string_view context, + reduction_options options, + bool large_count_available, + LargeCountCollective&& large_count_collective, + LegacyCollective&& legacy_collective) noexcept { + require_live_intracommunicator( + communicator, "bounded reduction requires a live intracommunicator"); + auto const buffers_are_distinct = + local_values.empty() || local_values.data() != reduced_values.data(); + validate_reduction_parameters(local_values.size(), reduced_values.size(), + buffers_are_distinct, kind, root, communicator, + options); + auto ignored_local_value = T{}; + auto ignored_reduced_value = T{}; + auto const* local_data = + local_values.empty() ? &ignored_local_value : local_values.data(); + auto* reduced_data = + reduced_values.empty() ? &ignored_reduced_value : reduced_values.data(); + auto const operation = reduction_operation(kind); + if (large_count_available && !options.force_mpi3 && + std::in_range(local_values.size())) { + check_or_abort(std::invoke(large_count_collective, local_data, reduced_data, + static_cast(local_values.size()), + operation, communicator.native_handle()), + communicator.native_handle(), context); + return; + } + + auto const ceiling = + std::min(options.mpi3_round_ceiling, + static_cast(std::numeric_limits::max())); + if (local_values.empty()) { + check_or_abort(std::invoke(legacy_collective, local_data, reduced_data, 0, + operation, communicator.native_handle()), + communicator.native_handle(), context); + return; + } + for (std::size_t offset = 0; offset < local_values.size();) { + auto const count = std::min(ceiling, local_values.size() - offset); + check_or_abort(std::invoke(legacy_collective, local_data + offset, + reduced_data + offset, static_cast(count), + operation, communicator.native_handle()), + communicator.native_handle(), context); + offset += count; + } +} +} // namespace detail + +template +void all_reduce_bounded(std::span local_values, + std::span global_values, + reduction_kind kind, + communicator_view communicator, + std::string_view context, + reduction_options options = {}) noexcept { +#if KAHIP_HAVE_MPI_ALLREDUCE_C + constexpr auto large_count_available = true; + auto const large_count_collective = + [](void const* send_buffer, void* receive_buffer, MPI_Count count, + MPI_Op operation, MPI_Comm native_communicator) noexcept { + return MPI_Allreduce_c(send_buffer, receive_buffer, count, + get_mpi_datatype(), operation, + native_communicator); + }; +#else + constexpr auto large_count_available = false; + auto const large_count_collective = [](void const*, void*, MPI_Count, MPI_Op, + MPI_Comm) noexcept { + return MPI_ERR_OTHER; + }; +#endif + auto const legacy_collective = [](void const* send_buffer, + void* receive_buffer, int count, + MPI_Op operation, + MPI_Comm native_communicator) noexcept { + return MPI_Allreduce(send_buffer, receive_buffer, count, + get_mpi_datatype(), operation, native_communicator); + }; + detail::reduce_bounded_impl( + std::span{local_values.data(), local_values.size()}, + std::span{global_values.data(), global_values.size()}, kind, + std::nullopt, communicator, context, options, large_count_available, + large_count_collective, legacy_collective); +} + +template + requires std::unsigned_integral> +void all_reduce_checked_sum(std::span local_values, + std::span global_values, + communicator_view communicator, + std::string_view context, + std::string_view overflow_boundary, + std::string_view overflow_diagnostic, + reduction_options options = {}) noexcept { + require_live_intracommunicator( + communicator, "checked sum reduction requires a live intracommunicator"); + auto const process_count = communicator.size(); + using value_type = std::remove_cv_t; + constexpr auto maximum = std::numeric_limits::max(); + if (process_count <= 0 || !std::in_range(process_count)) { + abort_on_capacity_failure( + communicator.native_handle(), overflow_boundary, + "communicator size exceeds checked-sum arithmetic capacity"); + } + auto const radix = static_cast(process_count); + if (radix > maximum / radix) { + abort_on_capacity_failure( + communicator.native_handle(), overflow_boundary, + "communicator size squared exceeds checked-sum arithmetic capacity"); + } + + try { + auto local_quotients = std::vector(local_values.size()); + auto local_remainders = std::vector(local_values.size()); + std::ranges::transform(local_values, local_quotients.begin(), + [radix](value_type value) { return value / radix; }); + std::ranges::transform(local_values, local_remainders.begin(), + [radix](value_type value) { return value % radix; }); + + auto global_quotients = std::vector(global_values.size()); + auto global_remainders = std::vector(global_values.size()); + all_reduce_bounded(std::span{local_quotients}, + std::span{global_quotients}, + reduction_kind::sum, communicator, context, options); + all_reduce_bounded(std::span{local_remainders}, + std::span{global_remainders}, + reduction_kind::sum, communicator, context, options); + + // For P ranks and radix P, every quotient sum is at most + // P*floor(max/P), and every remainder sum is below P^2. Both component + // reductions therefore remain representable. Only the exact + // reconstruction can overflow the destination type. + auto result = std::vector(global_values.size()); + for (std::size_t index = 0; index < result.size(); ++index) { + auto const quotient = global_quotients[index]; + auto const remainder = global_remainders[index]; + if (quotient > (maximum - remainder) / radix) { + abort_on_capacity_failure(communicator.native_handle(), + overflow_boundary, overflow_diagnostic); + } + result[index] = quotient * radix + remainder; + } + std::ranges::copy(result, global_values.begin()); + } catch (...) { + abort_on_exception(communicator.native_handle(), context); + } +} + +template +void reduce_bounded(std::span local_values, + std::span root_values, + reduction_kind kind, + int root, + communicator_view communicator, + std::string_view context, + reduction_options options = {}) noexcept { +#if KAHIP_HAVE_MPI_REDUCE_C + constexpr auto large_count_available = true; + auto const large_count_collective = + [root](void const* send_buffer, void* receive_buffer, MPI_Count count, + MPI_Op operation, MPI_Comm native_communicator) noexcept { + return MPI_Reduce_c(send_buffer, receive_buffer, count, + get_mpi_datatype(), operation, root, + native_communicator); + }; +#else + constexpr auto large_count_available = false; + auto const large_count_collective = [](void const*, void*, MPI_Count, MPI_Op, + MPI_Comm) noexcept { + return MPI_ERR_OTHER; + }; +#endif + auto const legacy_collective = [root](void const* send_buffer, + void* receive_buffer, int count, + MPI_Op operation, + MPI_Comm native_communicator) noexcept { + return MPI_Reduce(send_buffer, receive_buffer, count, get_mpi_datatype(), + operation, root, native_communicator); + }; + detail::reduce_bounded_impl( + std::span{local_values.data(), local_values.size()}, + std::span{root_values.data(), root_values.size()}, kind, root, + communicator, context, options, large_count_available, + large_count_collective, legacy_collective); +} + +template + requires(!std::is_same_v, bool>) +[[nodiscard]] auto all_reduce_sum(T local_value, + communicator_view communicator, + std::string_view context) noexcept -> T { + require_live_intracommunicator( + communicator, "fixed reduction requires a live intracommunicator"); + auto global_value = T{}; + check_or_abort( + MPI_Allreduce(&local_value, &global_value, 1, get_mpi_datatype(), + MPI_SUM, communicator.native_handle()), + communicator.native_handle(), context); + return global_value; +} +} // namespace parhip::mpi diff --git a/parallel/parallel_src/lib/communication/mpi_handles.h b/parallel/parallel_src/lib/communication/mpi_handles.h new file mode 100644 index 00000000..b843a3a9 --- /dev/null +++ b/parallel/parallel_src/lib/communication/mpi_handles.h @@ -0,0 +1,98 @@ +#pragma once + +#include + +#include + +#include "communication/mpi_failure.h" + +namespace parhip::mpi { +class communicator_view { + public: + explicit communicator_view(MPI_Comm communicator) noexcept + : communicator_(communicator) {} + + [[nodiscard]] auto native_handle() const noexcept -> MPI_Comm { + return communicator_; + } + + [[nodiscard]] auto rank() const noexcept -> int { + int result = 0; + check_or_abort(MPI_Comm_rank(communicator_, &result), communicator_, + "MPI_Comm_rank"); + return result; + } + + [[nodiscard]] auto size() const noexcept -> int { + int result = 0; + check_or_abort(MPI_Comm_size(communicator_, &result), communicator_, + "MPI_Comm_size"); + return result; + } + + private: + MPI_Comm communicator_; +}; + +inline void require_live_intracommunicator( + communicator_view communicator, + std::string_view diagnostic) noexcept { + if (!runtime_is_active()) { + abort_on_inactive_mpi_ownership(diagnostic); + } + if (communicator.native_handle() == MPI_COMM_NULL) { + abort_on_programming_error(communicator.native_handle(), diagnostic); + } + auto is_intercommunicator = 0; + check_or_abort( + MPI_Comm_test_inter(communicator.native_handle(), &is_intercommunicator), + communicator.native_handle(), + "MPI_Comm_test_inter(collective communicator)"); + if (is_intercommunicator != 0) { + abort_on_programming_error(communicator.native_handle(), diagnostic); + } +} + +class communicator { + public: + explicit communicator(communicator_view source); + ~communicator() noexcept; + + communicator(communicator const&) = delete; + auto operator=(communicator const&) -> communicator& = delete; + communicator(communicator&& other) noexcept; + auto operator=(communicator&& other) noexcept -> communicator&; + + [[nodiscard]] auto native_handle() const noexcept -> MPI_Comm { + return communicator_; + } + [[nodiscard]] auto view() const noexcept -> communicator_view { + return communicator_view{communicator_}; + } + + private: + void reset() noexcept; + + MPI_Comm communicator_ = MPI_COMM_NULL; +}; + +class topology { + public: + explicit topology(communicator_view source); + + topology(topology const&) = delete; + auto operator=(topology const&) -> topology& = delete; + topology(topology&&) noexcept = default; + auto operator=(topology&&) noexcept -> topology& = default; + + [[nodiscard]] auto native_handle() const noexcept -> MPI_Comm { + return communicator_.native_handle(); + } + [[nodiscard]] auto view() const noexcept -> communicator_view { + return communicator_.view(); + } + + private: + communicator communicator_; +}; +} // namespace parhip::mpi diff --git a/parallel/parallel_src/lib/communication/mpi_neighbors.cpp b/parallel/parallel_src/lib/communication/mpi_neighbors.cpp new file mode 100644 index 00000000..6f3c56a0 --- /dev/null +++ b/parallel/parallel_src/lib/communication/mpi_neighbors.cpp @@ -0,0 +1,168 @@ +#include "communication/mpi_neighbors.h" + +#include +#include + +namespace parhip::mpi { +distributed_graph::distributed_graph(communicator_view source, + std::vector outgoing_destinations) { + require_live_intracommunicator( + source, + "distributed graph construction requires a live intracommunicator"); + auto const rank = source.rank(); + auto const size = source.size(); + auto const local_destination_ranks_are_valid = std::ranges::all_of( + outgoing_destinations, [size](auto const destination) { + return destination >= 0 && destination < size; + }); + + std::ranges::sort(outgoing_destinations); + auto const unique_end = std::ranges::unique(outgoing_destinations); + outgoing_destinations.erase(unique_end.begin(), unique_end.end()); + auto const local_outdegree_is_representable = + std::in_range(outgoing_destinations.size()); + + { + auto validation_communicator = communicator{source}; + auto const collective_communicator = validation_communicator.view(); + auto const destination_ranks_are_valid = detail::collective_predicate( + local_destination_ranks_are_valid, collective_communicator); + // KAHIP_SEMANTIC_EXIT_BEGIN(distributed-graph-rank-domain) + if (!destination_ranks_are_valid) { + throw_collectively_agreed_semantic_error( + source.native_handle(), + "distributed graph destination validation failed"); + } + // KAHIP_SEMANTIC_EXIT_END(distributed-graph-rank-domain) + + auto local_capacity = capacity_result{}; + if (!local_outdegree_is_representable) { + local_capacity = with_fatal_capacity_issue( + local_capacity, capacity_issue::topology_degree_not_representable); + } + static_cast(resolve_capacity_collectively( + local_capacity, collective_communicator.native_handle(), + source.native_handle(), "distributed graph construction")); + } + + { + auto construction_communicator = communicator{source}; + auto const outdegree = static_cast(outgoing_destinations.size()); + // Although a zero-degree destination array is never dereferenced, some + // MPI implementations reject a null array argument. Keep the zero-degree + // topology portable with a valid ignored pointer. + auto const ignored_destination = rank; + auto const* destinations = outgoing_destinations.empty() + ? &ignored_destination + : outgoing_destinations.data(); + check_or_abort( + MPI_Dist_graph_create(construction_communicator.native_handle(), 1, + &rank, &outdegree, destinations, MPI_UNWEIGHTED, + MPI_INFO_NULL, 0, &communicator_), + construction_communicator.native_handle(), "MPI_Dist_graph_create"); + } + + auto const handler_result = + MPI_Comm_set_errhandler(communicator_, MPI_ERRORS_RETURN); + if (handler_result != MPI_SUCCESS) { + abort_on_mpi_error(communicator_, handler_result, + "MPI_Comm_set_errhandler(distributed graph)"); + } + + try { + auto topology_kind = MPI_UNDEFINED; + check_or_abort(MPI_Topo_test(communicator_, &topology_kind), communicator_, + "MPI_Topo_test(distributed graph)"); + if (topology_kind != MPI_DIST_GRAPH) { + abort_on_mpi_error(communicator_, MPI_ERR_TOPOLOGY, + "distributed graph topology verification"); + } + + auto indegree = 0; + auto outdegree = 0; + auto weighted = 0; + check_or_abort(MPI_Dist_graph_neighbors_count(communicator_, &indegree, + &outdegree, &weighted), + communicator_, "MPI_Dist_graph_neighbors_count"); + if (indegree < 0 || outdegree < 0) { + abort_on_mpi_error(communicator_, MPI_ERR_TOPOLOGY, + "distributed graph reported a negative degree"); + } + + sources_.resize(static_cast(indegree)); + destinations_.resize(static_cast(outdegree)); + check_or_abort(MPI_Dist_graph_neighbors( + communicator_, indegree, sources_.data(), MPI_UNWEIGHTED, + outdegree, destinations_.data(), MPI_UNWEIGHTED), + communicator_, "MPI_Dist_graph_neighbors"); + source_lookup_ = make_lookup(sources_); + destination_lookup_ = make_lookup(destinations_); + } catch (...) { + abort_on_exception(communicator_, "distributed graph local failure"); + } +} + +distributed_graph::~distributed_graph() noexcept { + reset(); +} + +distributed_graph::distributed_graph(distributed_graph&& other) noexcept + : communicator_(std::exchange(other.communicator_, MPI_COMM_NULL)), + sources_(std::move(other.sources_)), + destinations_(std::move(other.destinations_)), + source_lookup_(std::move(other.source_lookup_)), + destination_lookup_(std::move(other.destination_lookup_)) {} + +auto distributed_graph::operator=(distributed_graph&& other) noexcept + -> distributed_graph& { + if (this != &other) { + reset(); + communicator_ = std::exchange(other.communicator_, MPI_COMM_NULL); + sources_ = std::move(other.sources_); + destinations_ = std::move(other.destinations_); + source_lookup_ = std::move(other.source_lookup_); + destination_lookup_ = std::move(other.destination_lookup_); + } + return *this; +} + +auto distributed_graph::make_lookup(std::span ranks) + -> std::vector { + auto result = std::vector{}; + result.reserve(ranks.size()); + for (std::size_t index = 0; index < ranks.size(); ++index) { + result.emplace_back(ranks[index], index); + } + std::ranges::sort(result, {}, &rank_index::first); + return result; +} + +auto distributed_graph::find_index(std::span lookup, + int rank) noexcept + -> std::optional { + auto const position = + std::ranges::lower_bound(lookup, rank, {}, &rank_index::first); + if (position == lookup.end() || position->first != rank) { + return std::nullopt; + } + return position->second; +} + +void distributed_graph::reset() noexcept { + if (communicator_ != MPI_COMM_NULL) { + if (!runtime_is_active()) { + abort_on_inactive_mpi_ownership("distributed graph destruction"); + } + auto const free_result = MPI_Comm_free(&communicator_); + if (free_result != MPI_SUCCESS) { + abort_on_mpi_error(MPI_COMM_WORLD, free_result, + "MPI_Comm_free(distributed graph)"); + } + } + communicator_ = MPI_COMM_NULL; + sources_.clear(); + destinations_.clear(); + source_lookup_.clear(); + destination_lookup_.clear(); +} +} // namespace parhip::mpi diff --git a/parallel/parallel_src/lib/communication/mpi_neighbors.h b/parallel/parallel_src/lib/communication/mpi_neighbors.h new file mode 100644 index 00000000..a15f4659 --- /dev/null +++ b/parallel/parallel_src/lib/communication/mpi_neighbors.h @@ -0,0 +1,576 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_collectives.h" +#include "kahip_mpi_capabilities.h" + +namespace parhip::mpi { +class distributed_graph { + public: + explicit distributed_graph(communicator_view communicator, + std::vector outgoing_destinations); + ~distributed_graph() noexcept; + + distributed_graph(distributed_graph const&) = delete; + auto operator=(distributed_graph const&) -> distributed_graph& = delete; + distributed_graph(distributed_graph&& other) noexcept; + auto operator=(distributed_graph&& other) noexcept -> distributed_graph&; + + [[nodiscard]] auto native_handle() const noexcept -> MPI_Comm { + return communicator_; + } + [[nodiscard]] auto view() const noexcept -> communicator_view { + return communicator_view{communicator_}; + } + [[nodiscard]] auto sources() const noexcept -> std::span { + return sources_; + } + [[nodiscard]] auto destinations() const noexcept -> std::span { + return destinations_; + } + [[nodiscard]] auto source_index(int rank) const noexcept + -> std::optional { + return find_index(source_lookup_, rank); + } + [[nodiscard]] auto destination_index(int rank) const noexcept + -> std::optional { + return find_index(destination_lookup_, rank); + } + + private: + using rank_index = std::pair; + + [[nodiscard]] static auto make_lookup(std::span ranks) + -> std::vector; + [[nodiscard]] static auto find_index(std::span lookup, + int rank) noexcept + -> std::optional; + void reset() noexcept; + + MPI_Comm communicator_ = MPI_COMM_NULL; + std::vector sources_; + std::vector destinations_; + std::vector source_lookup_; + std::vector destination_lookup_; +}; + +namespace detail { +struct neighbor_count_exchange { + std::vector counts; + capacity_result capacity; +}; + +inline auto exchange_neighbor_counts(std::span send_counts, + std::size_t indegree, + communicator_view communicator) + -> neighbor_count_exchange { + static_assert(sizeof(std::size_t) <= sizeof(std::uint64_t)); + auto const outgoing = + std::vector(send_counts.begin(), send_counts.end()); + auto incoming = std::vector(indegree); + // Some MPI implementations validate the buffers before checking the degree. + auto const ignored_send = std::uint64_t{0}; + auto ignored_receive = std::uint64_t{0}; + check_or_abort( + MPI_Neighbor_alltoall( + outgoing.empty() ? &ignored_send : outgoing.data(), 1, MPI_UINT64_T, + incoming.empty() ? &ignored_receive : incoming.data(), 1, + MPI_UINT64_T, communicator.native_handle()), + communicator.native_handle(), + "MPI_Neighbor_alltoall(exchange neighbor counts)"); + + auto result = neighbor_count_exchange{ + .counts = std::vector(incoming.size()), + .capacity = {}, + }; + for (std::size_t index = 0; index < incoming.size(); ++index) { + if (!std::in_range(incoming[index])) { + result.capacity = with_fatal_capacity_issue( + result.capacity, capacity_issue::received_count_not_representable); + continue; + } + result.counts[index] = static_cast(incoming[index]); + } + return result; +} + +struct neighbor_receive_layout final { + std::vector offsets; + std::size_t element_count; + capacity_result capacity; +}; + +inline auto canonical_neighbor_layout(std::vector const& counts) + -> neighbor_receive_layout { + auto offsets = std::vector(counts.size()); + auto capacity = capacity_result{}; + auto total = std::size_t{0}; + auto remains_representable = true; + for (std::size_t index = 0; index < counts.size(); ++index) { + if (!remains_representable) { + continue; + } + offsets[index] = total; + if (counts[index] > std::numeric_limits::max() - total) { + capacity = with_fatal_capacity_issue( + capacity, capacity_issue::cumulative_offset_overflow); + remains_representable = false; + continue; + } + total += counts[index]; + } + return neighbor_receive_layout{ + .offsets = std::move(offsets), + .element_count = total, + .capacity = capacity, + }; +} + +template +[[nodiscard]] constexpr auto neighbor_capacity_preflight( + capacity_result local, + std::size_t receive_element_count, + bool direct_layout_is_representable) noexcept -> capacity_result { + if (receive_element_count > + std::numeric_limits::max() / sizeof(T)) { + local = with_fatal_capacity_issue( + local, capacity_issue::storage_byte_size_overflow); + } + if (!direct_layout_is_representable) { + local = with_bounded_capacity_issue( + local, capacity_issue::direct_backend_not_representable); + } + return local; +} + +[[nodiscard]] inline auto neighbor_mpi4_layout_is_representable_locally( + std::span send_counts, + std::span send_offsets, + std::span receive_counts, + std::span receive_offsets) noexcept -> bool { + auto const count_is_representable = [](std::size_t value) noexcept { + return std::in_range(value); + }; + auto const offset_is_representable = [](std::size_t value) noexcept { + return std::in_range(value); + }; + return std::ranges::all_of(send_counts, count_is_representable) && + std::ranges::all_of(receive_counts, count_is_representable) && + std::ranges::all_of(send_offsets, offset_is_representable) && + std::ranges::all_of(receive_offsets, offset_is_representable); +} + +[[nodiscard]] inline auto neighbor_mpi3_layout_is_representable_locally( + std::span send_counts, + std::span send_offsets, + std::span receive_counts, + std::span receive_offsets, + std::size_t ceiling) noexcept -> bool { + auto const is_representable = [ceiling](std::size_t value) noexcept { + return value <= ceiling; + }; + return std::ranges::all_of(send_counts, is_representable) && + std::ranges::all_of(receive_counts, is_representable) && + std::ranges::all_of(send_offsets, is_representable) && + std::ranges::all_of(receive_offsets, is_representable); +} + +inline auto bounded_round_count(std::size_t count, std::size_t ceiling) noexcept + -> std::size_t { + return count == 0 ? std::size_t{0} : (count - 1) / ceiling + std::size_t{1}; +} + +inline auto product_is_representable(std::size_t lhs, + std::size_t rhs, + std::size_t& result) noexcept -> bool { + if (lhs != 0 && rhs > std::numeric_limits::max() / lhs) { + result = 0; + return false; + } + result = lhs * rhs; + return true; +} + +inline auto sum_is_representable(std::size_t lhs, + std::size_t rhs, + std::size_t& result) noexcept -> bool { + if (rhs > std::numeric_limits::max() - lhs) { + result = 0; + return false; + } + result = lhs + rhs; + return true; +} + +struct mpi3_bounded_neighbor_phase final { + std::optional destination_index; + std::optional source_index; + std::size_t send_total = 0; + std::size_t receive_total = 0; + std::size_t round_count = 0; +}; + +struct mpi3_bounded_neighbor_plan final { + std::size_t ceiling = 0; + std::vector phases; +}; + +[[nodiscard]] inline auto make_mpi3_bounded_neighbor_plan( + std::span send_counts, + std::span send_offsets, + std::span receive_counts, + std::span receive_offsets, + std::size_t ceiling, + distributed_graph const& graph, + communicator_view communicator) -> mpi3_bounded_neighbor_plan { + auto const rank = static_cast(communicator.rank()); + auto const size = static_cast(communicator.size()); + auto result = mpi3_bounded_neighbor_plan{ + .ceiling = ceiling, + .phases = std::vector(size), + }; + auto local_capacity = capacity_result{}; + + for (std::size_t phase = 0; phase < size; ++phase) { + auto const distance_to_wrap = size - rank; + auto const destination_rank = + phase >= distance_to_wrap ? phase - distance_to_wrap : rank + phase; + auto const source_rank = + rank >= phase ? rank - phase : size - (phase - rank); + auto const destination_index = + graph.destination_index(static_cast(destination_rank)); + auto const source_index = graph.source_index(static_cast(source_rank)); + auto const send_total = destination_index.has_value() + ? send_counts[*destination_index] + : std::size_t{0}; + auto const receive_total = source_index.has_value() + ? receive_counts[*source_index] + : std::size_t{0}; + auto const local_rounds = + std::max(bounded_round_count(send_total, ceiling), + bounded_round_count(receive_total, ceiling)); + auto const local_rounds_u64 = static_cast(local_rounds); + auto phase_rounds_u64 = std::uint64_t{0}; + check_or_abort( + MPI_Allreduce(&local_rounds_u64, &phase_rounds_u64, 1, MPI_UINT64_T, + MPI_MAX, communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(MPI-3 bounded neighbor phase rounds)"); + if (phase_rounds_u64 > std::numeric_limits::max()) { + local_capacity = with_fatal_capacity_issue( + local_capacity, capacity_issue::bounded_round_arithmetic_overflow); + continue; + } + auto const phase_rounds = static_cast(phase_rounds_u64); + result.phases[phase] = mpi3_bounded_neighbor_phase{ + .destination_index = destination_index, + .source_index = source_index, + .send_total = send_total, + .receive_total = receive_total, + .round_count = phase_rounds, + }; + + auto ignored = std::size_t{0}; + if (phase_rounds != 0 && + !product_is_representable(phase_rounds - 1, ceiling, ignored)) { + local_capacity = with_fatal_capacity_issue( + local_capacity, capacity_issue::bounded_round_arithmetic_overflow); + } + if (destination_index.has_value() && send_total != 0) { + auto last_chunk_offset = std::size_t{0}; + auto last_storage_offset = std::size_t{0}; + auto const local_send_rounds = bounded_round_count(send_total, ceiling); + if (!product_is_representable(local_send_rounds - 1, ceiling, + last_chunk_offset) || + !sum_is_representable(send_offsets[*destination_index], + last_chunk_offset, last_storage_offset)) { + local_capacity = with_fatal_capacity_issue( + local_capacity, capacity_issue::bounded_round_arithmetic_overflow); + } + } + if (source_index.has_value() && receive_total != 0) { + auto last_chunk_offset = std::size_t{0}; + auto last_storage_offset = std::size_t{0}; + auto const local_receive_rounds = + bounded_round_count(receive_total, ceiling); + if (!product_is_representable(local_receive_rounds - 1, ceiling, + last_chunk_offset) || + !sum_is_representable(receive_offsets[*source_index], + last_chunk_offset, last_storage_offset)) { + local_capacity = with_fatal_capacity_issue( + local_capacity, capacity_issue::bounded_round_arithmetic_overflow); + } + } + } + + static_cast(resolve_capacity_collectively( + local_capacity, communicator.native_handle(), graph.native_handle(), + "neighbor_all_to_all_v bounded MPI-3 plan")); + return result; +} + +struct mpi3_bounded_neighbor_round final { + std::optional destination_index; + std::optional source_index; + std::optional send_storage_offset; + std::optional receive_storage_offset; + int send_count = 0; + int receive_count = 0; +}; + +[[nodiscard]] inline auto make_mpi3_bounded_neighbor_round( + mpi3_bounded_neighbor_plan const& plan, + std::size_t phase_index, + std::size_t round_index, + std::span send_offsets, + std::span receive_offsets) noexcept + -> mpi3_bounded_neighbor_round { + auto const& phase = plan.phases[phase_index]; + auto const chunk_offset = round_index * plan.ceiling; + auto const send_chunk = + chunk_offset < phase.send_total + ? std::min(plan.ceiling, phase.send_total - chunk_offset) + : std::size_t{0}; + auto const receive_chunk = + chunk_offset < phase.receive_total + ? std::min(plan.ceiling, phase.receive_total - chunk_offset) + : std::size_t{0}; + return mpi3_bounded_neighbor_round{ + .destination_index = phase.destination_index, + .source_index = phase.source_index, + .send_storage_offset = + send_chunk == 0 + ? std::nullopt + : std::optional{ + send_offsets[*phase.destination_index] + chunk_offset}, + .receive_storage_offset = + receive_chunk == 0 + ? std::nullopt + : std::optional{ + receive_offsets[*phase.source_index] + chunk_offset}, + .send_count = static_cast(send_chunk), + .receive_count = static_cast(receive_chunk), + }; +} + +template +void mpi3_bounded_neighbor_all_to_all_v( + segmented_buffer const& sends, + std::span receive_storage, + std::vector const& receive_counts, + std::vector const& receive_offsets, + MPI_Datatype datatype, + std::size_t ceiling, + distributed_graph const& graph, + communicator_view communicator) { + auto send_counts = std::vector(graph.destinations().size(), 0); + auto receive_counts_i = std::vector(graph.sources().size(), 0); + auto send_displacements = std::vector(graph.destinations().size(), 0); + auto receive_displacements = std::vector(graph.sources().size(), 0); + auto const plan = make_mpi3_bounded_neighbor_plan( + sends.counts(), sends.offsets(), receive_counts, receive_offsets, ceiling, + graph, communicator); + + for (std::size_t phase = 0; phase < plan.phases.size(); ++phase) { + for (std::size_t round = 0; round < plan.phases[phase].round_count; + ++round) { + std::ranges::fill(send_counts, 0); + std::ranges::fill(receive_counts_i, 0); + auto const layout = make_mpi3_bounded_neighbor_round( + plan, phase, round, sends.offsets(), receive_offsets); + + if (layout.destination_index.has_value()) { + send_counts[*layout.destination_index] = layout.send_count; + } + if (layout.source_index.has_value()) { + receive_counts_i[*layout.source_index] = layout.receive_count; + } + + auto const* send_buffer = + layout.send_storage_offset.has_value() + ? static_cast( + sends.storage().data() + *layout.send_storage_offset) + : static_cast(send_counts.data()); + auto* receive_buffer = + layout.receive_storage_offset.has_value() + ? static_cast(receive_storage.data() + + *layout.receive_storage_offset) + : static_cast(receive_counts_i.data()); + + check_or_abort(MPI_Neighbor_alltoallv( + send_buffer, send_counts.data(), + send_displacements.data(), datatype, receive_buffer, + receive_counts_i.data(), receive_displacements.data(), + datatype, communicator.native_handle()), + communicator.native_handle(), + "MPI_Neighbor_alltoallv(MPI-3 bounded neighbor round)"); + } + } +} +} // namespace detail + +template +[[nodiscard]] auto neighbor_all_to_all_v(segmented_buffer sends, + distributed_graph const& graph, + collective_options options = {}) + -> segmented_buffer { + auto semantic_failure = std::string_view{}; + auto result = std::optional>{}; + { + auto owned_communicator = communicator{graph.view()}; + auto const collective_communicator = owned_communicator.view(); + try { + auto const layout_is_valid = detail::collective_predicate( + sends.has_canonical_layout(graph.destinations().size()), + collective_communicator); + if (!layout_is_valid) { + semantic_failure = + "neighbor_all_to_all_v collective input validation failed"; + } else { + auto const mpi3_ceiling = detail::validate_collective_options( + options, collective_communicator); + if (!mpi3_ceiling.has_value()) { + semantic_failure = + "neighbor_all_to_all_v collective options must match and use " + "a nonzero MPI-3 ceiling"; + } else { + auto receive_count_exchange = detail::exchange_neighbor_counts( + sends.counts(), graph.sources().size(), collective_communicator); + auto receive_layout = + detail::canonical_neighbor_layout(receive_count_exchange.counts); + auto local_capacity = detail::combine_capacity_results( + receive_count_exchange.capacity, receive_layout.capacity); + auto const mpi4_is_candidate = + capabilities::has_neighbor_alltoallv_c && !options.force_mpi3; + auto const direct_layout_is_representable = + mpi4_is_candidate + ? detail::neighbor_mpi4_layout_is_representable_locally( + sends.counts(), sends.offsets(), + receive_count_exchange.counts, receive_layout.offsets) + : detail::neighbor_mpi3_layout_is_representable_locally( + sends.counts(), sends.offsets(), + receive_count_exchange.counts, receive_layout.offsets, + *mpi3_ceiling); + local_capacity = detail::neighbor_capacity_preflight( + local_capacity, receive_layout.element_count, + direct_layout_is_representable); + auto const route = resolve_capacity_collectively( + local_capacity, collective_communicator.native_handle(), + graph.native_handle(), "neighbor_all_to_all_v"); + + auto received = segmented_buffer::uninitialized( + receive_layout.element_count, + std::move(receive_count_exchange.counts), + std::move(receive_layout.offsets)); + auto datatype = + make_mpi_datatype(collective_communicator.native_handle()); + auto payload_complete = false; + +#if KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C + if (route == capacity_route::direct && mpi4_is_candidate) { + auto send_counts = std::vector{}; + auto receive_counts_c = std::vector{}; + auto send_offsets = std::vector{}; + auto receive_offsets_c = std::vector{}; + send_counts.reserve(sends.segment_count()); + send_offsets.reserve(sends.segment_count()); + receive_counts_c.reserve(received.segment_count()); + receive_offsets_c.reserve(received.segment_count()); + for (std::size_t index = 0; index < sends.segment_count(); + ++index) { + send_counts.push_back(detail::checked_mpi_count( + sends.counts()[index], "MPI neighbor send count")); + send_offsets.push_back(detail::checked_mpi_aint( + sends.offsets()[index], "MPI neighbor send offset")); + } + for (std::size_t index = 0; index < received.segment_count(); + ++index) { + receive_counts_c.push_back(detail::checked_mpi_count( + received.counts()[index], "MPI neighbor receive count")); + receive_offsets_c.push_back(detail::checked_mpi_aint( + received.offsets()[index], "MPI neighbor receive offset")); + } + check_or_abort( + MPI_Neighbor_alltoallv_c( + sends.storage().data(), send_counts.data(), + send_offsets.data(), datatype.native_handle(), + received.storage().data(), receive_counts_c.data(), + receive_offsets_c.data(), datatype.native_handle(), + collective_communicator.native_handle()), + collective_communicator.native_handle(), + "MPI_Neighbor_alltoallv_c(neighbor exchange)"); + payload_complete = true; + } +#endif + + if (!payload_complete && route == capacity_route::bounded) { + detail::mpi3_bounded_neighbor_all_to_all_v( + sends, received.storage(), received.counts(), + received.offsets(), datatype.native_handle(), *mpi3_ceiling, + graph, collective_communicator); + payload_complete = true; + } + if (!payload_complete) { + auto send_counts = std::vector{}; + auto receive_counts_i = std::vector{}; + auto send_offsets = std::vector{}; + auto receive_offsets_i = std::vector{}; + send_counts.reserve(sends.segment_count()); + send_offsets.reserve(sends.segment_count()); + receive_counts_i.reserve(received.segment_count()); + receive_offsets_i.reserve(received.segment_count()); + for (std::size_t index = 0; index < sends.segment_count(); + ++index) { + send_counts.push_back(detail::checked_int( + sends.counts()[index], "MPI neighbor send count")); + send_offsets.push_back(detail::checked_int( + sends.offsets()[index], "MPI neighbor send offset")); + } + for (std::size_t index = 0; index < received.segment_count(); + ++index) { + receive_counts_i.push_back(detail::checked_int( + received.counts()[index], "MPI neighbor receive count")); + receive_offsets_i.push_back(detail::checked_int( + received.offsets()[index], "MPI neighbor receive offset")); + } + check_or_abort( + MPI_Neighbor_alltoallv( + sends.storage().data(), send_counts.data(), + send_offsets.data(), datatype.native_handle(), + received.storage().data(), receive_counts_i.data(), + receive_offsets_i.data(), datatype.native_handle(), + collective_communicator.native_handle()), + collective_communicator.native_handle(), + "MPI_Neighbor_alltoallv(neighbor exchange)"); + } + result.emplace(std::move(received)); + } + } + } catch (...) { + abort_on_exception(collective_communicator.native_handle(), + "neighbor_all_to_all_v local failure"); + } + } + + // KAHIP_SEMANTIC_EXIT_BEGIN(sync-neighbor) + if (!semantic_failure.empty()) { + throw_collectively_agreed_semantic_error(graph.native_handle(), + semantic_failure); + } + // KAHIP_SEMANTIC_EXIT_END(sync-neighbor) + return std::move(*result); +} +} // namespace parhip::mpi diff --git a/parallel/parallel_src/lib/communication/mpi_tools.cpp b/parallel/parallel_src/lib/communication/mpi_tools.cpp index e6a5646b..061fb878 100644 --- a/parallel/parallel_src/lib/communication/mpi_tools.cpp +++ b/parallel/parallel_src/lib/communication/mpi_tools.cpp @@ -1,260 +1,865 @@ /****************************************************************************** * mpi_tools.cpp - * * + * * Source of KaHIP -- Karlsruhe High Quality Partitioning. * Christian Schulz *****************************************************************************/ -#include -#include +#include "communication/mpi_tools.h" -#include "io/parallel_vector_io.h" -#include "io/parallel_graph_io.h" -#include "mpi_tools.h" +#include -mpi_tools::mpi_tools() { - +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_fixed_broadcast.h" +#include "communication/mpi_fixed_reduction.h" +#include "communication/serial_kernel_profile_observer.h" +#include "serial_kernel_structure.h" +#include "tools/fatal_diagnostics.h" + +namespace parhip { +namespace { +struct serial_kernel_profile_observer_state final { + mpi_tools_detail::serial_kernel_profile_observer_callback callback{}; + void* context{}; +}; + +auto serial_kernel_profile_observer = serial_kernel_profile_observer_state{}; } -mpi_tools::~mpi_tools() { - - +namespace mpi_tools_detail { +scoped_serial_kernel_profile_observer::scoped_serial_kernel_profile_observer( + serial_kernel_profile_observer_callback callback, + void* context) noexcept + : previous_{serial_kernel_profile_observer.callback}, + previous_context_{serial_kernel_profile_observer.context} { + serial_kernel_profile_observer.callback = callback; + serial_kernel_profile_observer.context = context; } -// currently this method is for debugging purposses only -// later on this may be a parallel io routine -void mpi_tools::collect_and_write_labels( MPI_Comm communicator, PPartitionConfig & config, parallel_graph_access & G) { - int rank, size; - MPI_Comm_rank( communicator, &rank); - MPI_Comm_size( communicator, &size); - - std::vector< NodeID > labels; - - if( rank == ROOT ) { - labels.resize(G.number_of_global_nodes()); - forall_local_nodes(G, node) { - labels[node] = G.getNodeLabel(node); - } endfor - } else { - //pack the data - forall_local_nodes(G, node) { - labels.push_back(G.getGlobalID(node)); - labels.push_back(G.getNodeLabel(node)); - } endfor - } - - if( rank == ROOT ) { - int counter = 0; - while( counter < size-1) { - // wait for incomming message of an adjacent processor - int flag; MPI_Status st; - MPI_Iprobe(MPI_ANY_SOURCE, rank, communicator, &flag, &st); - - while( flag ) { - int message_length; - MPI_Get_count(&st, MPI_UNSIGNED_LONG_LONG, &message_length); - std::vector message; message.resize(message_length); - - MPI_Status rst; - MPI_Recv( &message[0], message_length, MPI_UNSIGNED_LONG_LONG, st.MPI_SOURCE, st.MPI_TAG, communicator, &rst); - counter++; - - for( int i = 0; i < message_length-1; i+=2) { - NodeID global_id = message[i]; - NodeID label = message[i+1]; - - labels[global_id] = label; - } - MPI_Iprobe(MPI_ANY_SOURCE, rank, communicator, &flag, &st); - } - } - - } else { - MPI_Request rq; - MPI_Isend( &labels[0], labels.size(), MPI_UNSIGNED_LONG_LONG, ROOT, rank+12*size, communicator, &rq); - } - - if( rank == ROOT ) { - std::string clustering_filename("tmpclustering"); - parallel_vector_io pvio; - pvio.writeVectorSequentially(labels, clustering_filename); - } - MPI_Barrier(communicator); +scoped_serial_kernel_profile_observer::~scoped_serial_kernel_profile_observer() noexcept { + serial_kernel_profile_observer.callback = previous_; + serial_kernel_profile_observer.context = previous_context_; } +void observe_checked_serial_kernel_profile( + kahip::serial_kernel::serial_kernel_profile const& profile) noexcept { + if (serial_kernel_profile_observer.callback != nullptr) { + serial_kernel_profile_observer.callback(serial_kernel_profile_observer.context, + profile); + } +} +} // namespace mpi_tools_detail + +namespace { +using graph_node_record = mpi_tools_detail::complete_graph_node_record; +using graph_edge_record = mpi_tools_detail::complete_graph_edge_record; +using serial_kernel_profile = kahip::serial_kernel::serial_kernel_profile; +using serial_profile_input = kahip::serial_kernel::profile_input; +using serial_profile_limits = kahip::serial_kernel::profile_limits; +using serial_profile_reason = kahip::serial_kernel::profile_reason; + +struct local_serial_observation final { + std::uint64_t local_nodes{}; + std::uint64_t local_edges{}; + std::uint64_t total_node_weight{}; + std::uint64_t maximum_node_weight{}; + std::uint64_t total_edge_weight{}; + std::uint64_t maximum_edge_weight{}; + std::uint64_t reported_global_nodes{}; + std::uint64_t reported_global_edges{}; + std::uint64_t block_count{}; + std::uint64_t absolute_bound{}; + std::uint64_t bank_factor_twice{}; + std::uint64_t flags{}; +}; + +constexpr auto csr_offsets_are_valid = std::uint64_t{1} << 0; +constexpr auto targets_are_valid = std::uint64_t{1} << 1; +constexpr auto labels_are_valid = std::uint64_t{1} << 2; +constexpr auto sums_are_valid = std::uint64_t{1} << 3; +constexpr auto social_mode = std::uint64_t{1} << 4; +constexpr auto all_local_observation_flags = csr_offsets_are_valid | + targets_are_valid | + labels_are_valid | + sums_are_valid; + +[[nodiscard]] auto native_serial_profile_limits() -> serial_profile_limits { + auto result = serial_profile_limits::native(); + result.xadj_elements = std::vector{}.max_size(); + result.adjncy_elements = std::vector{}.max_size(); + result.node_weight_elements = std::vector{}.max_size(); + result.edge_weight_elements = std::vector{}.max_size(); + result.partition_elements = std::vector{}.max_size(); + result.flat_payload_elements = std::vector{}.max_size(); + result.structural_validation_elements = + std::vector{}.max_size(); + result.wire_node_elements = std::vector{}.max_size(); + result.wire_edge_elements = std::vector{}.max_size(); + result.complete_node_elements = std::vector{}.max_size(); + result.complete_node_data_elements = std::vector{}.max_size(); + result.complete_edge_elements = std::vector{}.max_size(); + result.wire_node_bytes = sizeof(graph_node_record); + result.wire_edge_bytes = sizeof(graph_edge_record); + result.complete_node_bytes = sizeof(Node); + result.complete_node_data_bytes = sizeof(NodeData); + result.complete_edge_bytes = sizeof(Edge); + result.structural_validation_arc_bytes = + sizeof(kahip::serial_kernel::directed_arc); + return result; +} -void mpi_tools::collect_parallel_graph_to_local_graph( MPI_Comm communicator, PPartitionConfig & config, - parallel_graph_access & G, - complete_graph_access & Q) { - - int rank, size; - MPI_Comm_rank( communicator, &rank); - MPI_Comm_size( communicator, &size); - - std::vector< NodeID > message; - - if( rank == ROOT ) { - Q.start_construction( G.number_of_global_nodes(), G.number_of_global_edges(), - G.number_of_global_nodes(), G.number_of_global_edges(), false); // no update of comm_rounds! - Q.set_range(0, G.number_of_global_nodes()); // this graph should contain all global edges - forall_local_nodes(G, node) { - NodeID cur_node = Q.new_node(); - Q.setNodeWeight(cur_node, G.getNodeWeight(node)); - Q.setSecondPartitionIndex(cur_node, G.getSecondPartitionIndex(node)); - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - EdgeID e_bar = Q.new_edge(cur_node, G.getGlobalID(target)); - Q.setEdgeWeight(e_bar, G.getEdgeWeight(e)); - } endfor - } endfor - } else { - // layout: no local nodes, no local edges, node_1, pidx, weight, degree,its edges: e_1, w_1, ...,node_2, ... - message.push_back(G.number_of_local_nodes()); - forall_local_nodes(G, node) { - //message.push_back(G.getGlobalID(node)); - message.push_back(G.getSecondPartitionIndex(node)); - message.push_back(G.getNodeWeight(node)); - message.push_back(G.getNodeDegree(node)); - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - message.push_back(G.getGlobalID(target)); - message.push_back(G.getEdgeWeight(e)); - } endfor - } endfor - } - - if( rank == ROOT) { - for( int i = 1; i < size; i++) { - int flag; MPI_Status st; - MPI_Iprobe(i, 13*size, communicator, &flag, &st); - - while(!flag) { MPI_Iprobe(i, 13*size, communicator, &flag, &st); } - - int message_length; - MPI_Get_count(&st, MPI_UNSIGNED_LONG_LONG, &message_length); - std::vector rmessage; rmessage.resize(message_length); - - MPI_Status rst; - MPI_Recv( &rmessage[0], message_length, MPI_UNSIGNED_LONG_LONG, st.MPI_SOURCE, st.MPI_TAG, communicator, &rst); - - NodeID no_nodes = rmessage[0]; - NodeID pos = 1; - for( ULONG node = 0; node < no_nodes; node++) { - NodeID cur_node = Q.new_node(); - Q.setSecondPartitionIndex(cur_node, rmessage[pos++]); - Q.setNodeWeight(cur_node, rmessage[pos++]); - - EdgeID degree = rmessage[pos++]; - for( ULONG e = 0; e < degree; e++) { - EdgeID e_bar = Q.new_edge(cur_node, rmessage[pos++]); - Q.setEdgeWeight(e_bar, rmessage[pos++]); - } - } - - } - } else { - MPI_Request rq; - MPI_Isend( &message[0], message.size(), MPI_UNSIGNED_LONG_LONG, ROOT, 13*size, communicator, &rq); - } - - if( rank == ROOT ) { - Q.finish_construction(); - } - - MPI_Barrier(communicator); +[[nodiscard]] auto observe_serial_kernel(parallel_graph_access& graph, + PPartitionConfig const& config) + -> local_serial_observation { + auto result = local_serial_observation{ + .local_nodes = static_cast(graph.number_of_local_nodes()), + .local_edges = static_cast(graph.number_of_local_edges()), + .reported_global_nodes = + static_cast(graph.number_of_global_nodes()), + .reported_global_edges = + static_cast(graph.number_of_global_edges()), + .block_count = static_cast(config.k), + .absolute_bound = static_cast(config.upper_bound_partition), + .bank_factor_twice = + (config.initial_partitioning_algorithm == + InitialPartitioningAlgorithm::KAFFPAESTRONG || + config.initial_partitioning_algorithm == + InitialPartitioningAlgorithm::KAFFPAESTRONGSNW) + ? std::uint64_t{6} + : (config.initial_partitioning_algorithm == + InitialPartitioningAlgorithm::KAFFPAEFAST + ? std::uint64_t{2} + : std::uint64_t{3}), + .flags = all_local_observation_flags, + }; + if (config.initial_partitioning_algorithm == + InitialPartitioningAlgorithm::KAFFPAEULTRAFASTSNW || + config.initial_partitioning_algorithm == + InitialPartitioningAlgorithm::KAFFPAEFASTSNW || + config.initial_partitioning_algorithm == + InitialPartitioningAlgorithm::KAFFPAEECOSNW || + config.initial_partitioning_algorithm == + InitialPartitioningAlgorithm::KAFFPAESTRONGSNW) { + result.flags |= social_mode; + } + auto expected_edge = EdgeID{0}; + for (NodeID node = 0; node < graph.number_of_local_nodes(); ++node) { + auto const first = graph.get_first_edge(node); + auto const end = graph.get_first_invalid_edge(node); + if (first != expected_edge || end < first || + end > graph.number_of_local_edges() || + end > static_cast(std::numeric_limits::max())) { + result.flags &= ~csr_offsets_are_valid; + } + expected_edge = end; + auto const node_weight = graph.getNodeWeight(node); + result.maximum_node_weight = std::max( + result.maximum_node_weight, static_cast(node_weight)); + if (!kahip::serial_kernel::checked_add( + result.total_node_weight, static_cast(node_weight), + std::numeric_limits::max(), result.total_node_weight)) { + result.flags &= ~sums_are_valid; + } + if (config.vcycle && + (config.k == 0 || graph.getSecondPartitionIndex(node) >= config.k)) { + result.flags &= ~labels_are_valid; + } + for (EdgeID edge = first; edge < end && edge < graph.number_of_local_edges(); + ++edge) { + auto const target = graph.getGlobalID(graph.getEdgeTarget(edge)); + if (target > static_cast(std::numeric_limits::max())) { + result.flags &= ~targets_are_valid; + } + auto const edge_weight = graph.getEdgeWeight(edge); + result.maximum_edge_weight = std::max( + result.maximum_edge_weight, static_cast(edge_weight)); + if (!kahip::serial_kernel::checked_add( + result.total_edge_weight, + static_cast(edge_weight), + std::numeric_limits::max(), + result.total_edge_weight)) { + result.flags &= ~sums_are_valid; + } + } + } + if (expected_edge != graph.number_of_local_edges()) { + result.flags &= ~csr_offsets_are_valid; + } + return result; } +[[nodiscard]] auto serial_profile_from_reductions( + local_serial_observation const& root_observation, + std::array const& sums, + std::array const& maxima, + std::array const& valid, + bool sums_are_exact, bool common_metadata) -> serial_kernel_profile { + auto const input = serial_profile_input{ + .global_nodes = sums[0], + .global_directed_edges = sums[1], + .total_node_weight = sums[2], + .maximum_node_weight = maxima[0], + .total_directed_edge_weight = sums[3], + .maximum_directed_edge_weight = maxima[1], + .block_count = root_observation.block_count, + .absolute_bound = root_observation.absolute_bound, + .csr_offsets_are_valid = valid[0] != 0, + .targets_are_valid = valid[1] != 0, + .labels_are_valid = valid[2] != 0, + .social_mode = (root_observation.flags & social_mode) != 0, + .bank_factor_twice = root_observation.bank_factor_twice, + }; + auto profile = kahip::serial_kernel::make_profile( + input, native_serial_profile_limits()); + if (!sums_are_exact || valid[3] == 0) { + profile.reason = serial_profile_reason::collective_aggregate_overflow; + } else if (!common_metadata) { + profile.reason = serial_profile_reason::collective_configuration_mismatch; + } else if (input.global_nodes != root_observation.reported_global_nodes) { + profile.reason = serial_profile_reason::global_node_count_mismatch; + } else if (input.global_directed_edges != root_observation.reported_global_edges) { + profile.reason = serial_profile_reason::global_directed_edge_count_mismatch; + } + return profile; +} +[[noreturn]] void abort_unsafe_serial_profile( + MPI_Comm communicator, + int rank, + serial_kernel_profile const& profile) noexcept { + if (rank == static_cast(ROOT)) { + kahip::diagnostics::critical( + "ParHIP serial kernel profile failure: reason=", + kahip::serial_kernel::reason_name(profile.reason), + ", global nodes=", profile.global_nodes, + ", global directed edges=", profile.global_directed_edges, + ", total node weight=", profile.total_node_weight, + ", maximum node weight=", profile.maximum_node_weight, + ", total directed edge weight=", profile.total_directed_edge_weight, + ", maximum directed edge weight=", profile.maximum_directed_edge_weight, + ", block count=", profile.block_count, + ", absolute bound=", profile.absolute_bound, + ", wire record bytes=", profile.wire_record_bytes, + ", CSR bytes=", profile.csr_bytes, + ", partition bytes=", profile.partition_bytes, + ", serial input bytes=", profile.serial_input_bytes, + ", complete graph bytes=", profile.complete_graph_bytes, + ", structural validation bytes=", profile.structural_validation_bytes, + ", base memory bytes=", profile.base_memory_bytes, + ", flat payload elements=", profile.flat_payload_elements); + } + static_cast(MPI_Abort(communicator, EXIT_FAILURE)); + std::abort(); +} -void mpi_tools::distribute_local_graph( MPI_Comm communicator, PPartitionConfig & config, - complete_graph_access & G) { +static_assert(std::numeric_limits::digits <= + std::numeric_limits::digits); +static_assert(std::numeric_limits::digits <= + std::numeric_limits::digits); +static_assert(std::numeric_limits::digits <= + std::numeric_limits::digits); +static_assert(std::numeric_limits::digits <= + std::numeric_limits::digits); + +[[nodiscard]] auto packed_graph_capacity(parallel_graph_access& graph) noexcept + -> mpi::capacity_result { + auto result = mpi::capacity_result{}; + auto const local_nodes = graph.number_of_local_nodes(); + auto const local_edges = graph.number_of_local_edges(); + if (!std::in_range(local_nodes) || + !std::in_range(local_edges)) { + return mpi::with_fatal_capacity_issue( + result, mpi::capacity_issue::storage_byte_size_overflow); + } + auto const nodes = static_cast(local_nodes); + auto const edges = static_cast(local_edges); + if (nodes > + std::numeric_limits::max() / sizeof(graph_node_record) || + edges > + std::numeric_limits::max() / sizeof(graph_edge_record)) { + return mpi::with_fatal_capacity_issue( + result, mpi::capacity_issue::storage_byte_size_overflow); + } + return result; +} - int rank; - MPI_Comm_rank( communicator, &rank); +struct packed_local_graph final { + std::vector nodes; + std::vector edges; +}; + +[[nodiscard]] auto pack_local_graph(parallel_graph_access& graph) + -> packed_local_graph { + auto const local_nodes = graph.number_of_local_nodes(); + auto const local_edges = graph.number_of_local_edges(); + auto result = packed_local_graph{}; + result.nodes.reserve(static_cast(local_nodes)); + result.edges.reserve(static_cast(local_edges)); + + for (NodeID node = 0; node < local_nodes; ++node) { + auto const global = graph.getGlobalID(node); + auto const degree = graph.getNodeDegree(node); + result.nodes.push_back(graph_node_record{ + .global_id = static_cast(global), + .second_partition = + static_cast(graph.getSecondPartitionIndex(node)), + .weight = static_cast(graph.getNodeWeight(node)), + .degree = static_cast(degree), + }); + auto const first_edge = graph.get_first_edge(node); + auto const edge_end = graph.get_first_invalid_edge(node); + for (auto edge = first_edge; edge < edge_end; ++edge) { + auto const target = graph.getGlobalID(graph.getEdgeTarget(edge)); + result.edges.push_back(graph_edge_record{ + .target_global_id = static_cast(target), + .weight = static_cast(graph.getEdgeWeight(edge)), + }); + } + } + return result; +} - //first B-Cast number of nodes and number of edges - ULONG number_of_nodes = 0; - ULONG number_of_edges = 0; +template +[[nodiscard]] auto make_root_exchange(std::vector records, + std::size_t communicator_size) + -> mpi::segmented_buffer { + auto counts = std::vector(communicator_size, 0); + counts[static_cast(ROOT)] = records.size(); + auto offsets = std::vector(communicator_size); + std::exclusive_scan(counts.begin(), counts.end(), offsets.begin(), + std::size_t{0}); + return mpi::segmented_buffer{std::move(records), std::move(counts), + std::move(offsets)}; +} - std::vector< int > buffer(2,0); - if(rank == (int)ROOT) { - buffer[0] = G.number_of_global_nodes(); - buffer[1] = G.number_of_global_edges(); +[[nodiscard]] auto complete_graph_payload_is_valid( + mpi::segmented_buffer const& received_nodes, + mpi::segmented_buffer const& received_edges, + NodeID global_nodes, + EdgeID global_edges, + std::size_t communicator_size, + bool require_serial_kernel_structure) -> bool { + if (received_nodes.segment_count() != communicator_size || + received_edges.segment_count() != communicator_size || + !std::in_range(global_edges)) { + return false; + } + + auto next_global_node = std::uint64_t{0}; + auto total_received_edges = std::uint64_t{0}; + auto arcs = std::vector{}; + if (require_serial_kernel_structure) { + arcs.reserve(static_cast(global_edges)); + } + for (std::size_t source = 0; source < communicator_size; ++source) { + auto const node_segment = received_nodes.segment(source); + auto const edge_segment = received_edges.segment(source); + auto parsed_edges = std::size_t{0}; + for (auto const& node : node_segment) { + if (node.global_id != next_global_node || + !std::in_range(node.degree)) { + return false; + } + auto const degree = static_cast(node.degree); + if (degree > edge_segment.size() - parsed_edges) { + return false; + } + for (std::size_t edge_index = 0; edge_index < degree; ++edge_index) { + auto const& edge = edge_segment[parsed_edges + edge_index]; + if (edge.target_global_id >= static_cast(global_nodes)) { + return false; } - MPI_Bcast(&buffer[0], 2, MPI_INT, ROOT, communicator); - - number_of_nodes = buffer[0]; - number_of_edges = buffer[1]; - - int* xadj; - int* adjncy; - int* vwgt; - int* adjwgt; + if (require_serial_kernel_structure) { + arcs.push_back(kahip::serial_kernel::directed_arc{ + .source = next_global_node, + .target = edge.target_global_id, + .weight = edge.weight, + }); + } + } + parsed_edges += degree; + ++next_global_node; + } + if (parsed_edges != edge_segment.size() || + !std::in_range(edge_segment.size())) { + return false; + } + auto const received_edge_count = + static_cast(edge_segment.size()); + if (total_received_edges > + std::numeric_limits::max() - received_edge_count) { + return false; + } + total_received_edges += received_edge_count; + } + return next_global_node == static_cast(global_nodes) && + total_received_edges == static_cast(global_edges) && + (!require_serial_kernel_structure || + kahip::serial_kernel::is_loop_free_reciprocal_undirected( + std::move(arcs))); +} - if( rank == (int)ROOT) { - xadj = G.UNSAFE_metis_style_xadj_array(); - adjncy = G.UNSAFE_metis_style_adjncy_array(); +void construct_complete_graph( + complete_graph_access& complete, + mpi::segmented_buffer const& received_nodes, + mpi::segmented_buffer const& received_edges, + NodeID global_nodes, + EdgeID global_edges) { + complete.start_construction(global_nodes, global_edges, global_nodes, + global_edges, false); + complete.set_range(0, global_nodes); + for (std::size_t source = 0; source < received_nodes.segment_count(); + ++source) { + auto const node_segment = received_nodes.segment(source); + auto const edge_segment = received_edges.segment(source); + auto edge_position = std::size_t{0}; + for (auto const& node_record : node_segment) { + auto const node = complete.new_node(); + complete.setSecondPartitionIndex( + node, static_cast(node_record.second_partition)); + complete.setNodeWeight(node, static_cast(node_record.weight)); + auto const degree = static_cast(node_record.degree); + for (std::size_t edge_index = 0; edge_index < degree; ++edge_index) { + auto const& edge_record = edge_segment[edge_position++]; + auto const edge = complete.new_edge( + node, static_cast(edge_record.target_global_id)); + complete.setEdgeWeight(edge, + static_cast(edge_record.weight)); + } + } + } + complete.finish_construction(); +} - vwgt = G.UNSAFE_metis_style_vwgt_array(); - adjwgt = G.UNSAFE_metis_style_adjwgt_array(); - } else { - xadj = new int[number_of_nodes+1]; - adjncy = new int[number_of_edges]; +enum class distribution_status : std::uint64_t { + valid, + incomplete_graph, + serial_capacity_exceeded, +}; + +[[nodiscard]] auto root_distribution_status(complete_graph_access& graph) + -> distribution_status { + auto const global_nodes = graph.number_of_global_nodes(); + auto const global_edges = graph.number_of_global_edges(); + if (graph.number_of_local_nodes() != global_nodes || + graph.number_of_local_edges() != global_edges) { + return distribution_status::incomplete_graph; + } + if (!std::in_range(global_nodes) || !std::in_range(global_edges)) { + return distribution_status::serial_capacity_exceeded; + } + + auto expected_edge = EdgeID{0}; + for (NodeID node = 0; node < global_nodes; ++node) { + auto const first_edge = graph.get_first_edge(node); + auto const edge_end = graph.get_first_invalid_edge(node); + if (first_edge != expected_edge || edge_end < first_edge || + edge_end > global_edges) { + return distribution_status::incomplete_graph; + } + if (!std::in_range(graph.getNodeWeight(node))) { + return distribution_status::serial_capacity_exceeded; + } + for (auto edge = first_edge; edge < edge_end; ++edge) { + if (graph.getEdgeTarget(edge) >= global_nodes) { + return distribution_status::incomplete_graph; + } + if (!std::in_range(graph.getEdgeTarget(edge)) || + !std::in_range(graph.getEdgeWeight(edge))) { + return distribution_status::serial_capacity_exceeded; + } + } + expected_edge = edge_end; + } + return expected_edge == global_edges ? distribution_status::valid + : distribution_status::incomplete_graph; +} - vwgt = new int[number_of_nodes]; - adjwgt = new int[number_of_edges]; - } - MPI_Bcast(xadj, number_of_nodes+1, MPI_INT, ROOT, communicator); - MPI_Bcast(adjncy, number_of_edges, MPI_INT, ROOT, communicator); - MPI_Bcast(vwgt, number_of_nodes, MPI_INT, ROOT, communicator); - MPI_Bcast(adjwgt, number_of_edges, MPI_INT, ROOT, communicator); +[[nodiscard]] auto checked_payload_size(std::size_t nodes, + std::size_t edges, + MPI_Comm communicator) noexcept + -> std::size_t { + auto const maximum = std::numeric_limits::max(); + auto total = nodes; + auto const add = [&](std::size_t count) noexcept { + if (count > maximum - total) { + mpi::abort_on_capacity_failure( + communicator, "complete graph distribution", + "serial graph payload size exceeds local size_t capacity"); + } + total += count; + }; + add(std::size_t{1}); + add(edges); + add(nodes); + add(edges); + if (total > std::vector{}.max_size()) { + mpi::abort_on_capacity_failure( + communicator, "complete graph distribution", + "serial graph payload exceeds vector::max_size()"); + } + return total; +} +} // namespace + +void collect_parallel_graph_to_local_graph_impl( + MPI_Comm communicator, + PPartitionConfig const& config, + parallel_graph_access& distributed, + complete_graph_access& complete, + bool require_serial_kernel_structure); + +auto mpi_tools::preflight_serial_kernel( + MPI_Comm communicator, + PPartitionConfig const& config, + parallel_graph_access& graph) const -> serial_kernel_profile { + auto const collective = mpi::communicator_view{communicator}; + mpi::require_live_intracommunicator( + collective, "serial kernel profile requires a live intracommunicator"); + auto const local = observe_serial_kernel(graph, config); + auto const rank = collective.rank(); + auto const local_sums = std::array{ + local.local_nodes, local.local_edges, local.total_node_weight, + local.total_edge_weight}; + auto const local_maxima = std::array{ + local.maximum_node_weight, local.maximum_edge_weight}; + auto const local_validity = std::array{ + (local.flags & csr_offsets_are_valid) != 0, + (local.flags & targets_are_valid) != 0, + (local.flags & labels_are_valid) != 0, + (local.flags & sums_are_valid) != 0, + }; + auto global_maxima = std::array{}; + auto global_validity = std::array{}; + mpi::all_reduce_bounded(std::span{local_maxima}, + std::span{global_maxima}, + mpi::reduction_kind::maximum, collective, + "MPI_Allreduce(serial kernel profile maxima)"); + mpi::all_reduce_bounded(std::span{local_validity}, + std::span{global_validity}, + mpi::reduction_kind::minimum, collective, + "MPI_Allreduce(serial kernel profile validity)"); + + // The public C API derives config.seed per rank before this handoff. It is + // intentionally not collective metadata; the remaining values select the + // same serial-kernel control flow on every rank. + auto const local_metadata = std::array{ + local.reported_global_nodes, local.reported_global_edges, + local.block_count, local.absolute_bound, local.bank_factor_twice, + (local.flags & social_mode) != 0, + config.vcycle ? std::uint64_t{1} : std::uint64_t{0}, + static_cast(config.initial_partitioning_algorithm), + static_cast(config.inbalance), + static_cast(config.evolutionary_time_limit)}; + auto minimum_metadata = std::array{}; + auto maximum_metadata = std::array{}; + mpi::all_reduce_bounded(std::span{local_metadata}, + std::span{minimum_metadata}, + mpi::reduction_kind::minimum, collective, + "MPI_Allreduce(serial kernel profile metadata min)"); + mpi::all_reduce_bounded(std::span{local_metadata}, + std::span{maximum_metadata}, + mpi::reduction_kind::maximum, collective, + "MPI_Allreduce(serial kernel profile metadata max)"); + auto const common_metadata = minimum_metadata == maximum_metadata; + + auto root_sums = std::vector>{}; + if (rank == static_cast(ROOT)) { + try { + root_sums.resize(static_cast(collective.size())); + } catch (...) { + mpi::abort_on_exception(communicator, + "serial kernel profile root sum allocation failure"); + } + } + mpi::check_or_abort( + MPI_Gather(local_sums.data(), static_cast(local_sums.size()), + MPI_UINT64_T, + rank == static_cast(ROOT) ? root_sums.data() : nullptr, + static_cast(local_sums.size()), MPI_UINT64_T, ROOT, + communicator), + communicator, "MPI_Gather(serial kernel profile sums)"); + + auto profile = serial_kernel_profile{}; + if (rank == static_cast(ROOT)) { + auto sums = std::array{}; + auto sums_are_exact = true; + for (auto const& rank_sums : root_sums) { + for (auto index = std::size_t{0}; index < sums.size(); ++index) { + sums_are_exact = sums_are_exact && kahip::serial_kernel::checked_add( + sums[index], rank_sums[index], + std::numeric_limits::max(), sums[index]); + } + } + profile = serial_profile_from_reductions( + local, sums, global_maxima, global_validity, sums_are_exact, + common_metadata); + } + auto packed = std::array{ + profile.global_nodes, + profile.global_directed_edges, + profile.total_node_weight, + profile.maximum_node_weight, + profile.total_directed_edge_weight, + profile.maximum_directed_edge_weight, + profile.block_count, + profile.absolute_bound, + profile.wire_record_bytes, + profile.csr_bytes, + profile.partition_bytes, + profile.serial_input_bytes, + profile.complete_graph_bytes, + profile.structural_validation_bytes, + profile.base_memory_bytes, + profile.flat_payload_elements, + static_cast(profile.reason), + }; + mpi::check_or_abort( + MPI_Bcast(packed.data(), static_cast(packed.size()), MPI_UINT64_T, + ROOT, communicator), + communicator, "MPI_Bcast(serial kernel profile)"); + profile = serial_kernel_profile{ + .global_nodes = packed[0], + .global_directed_edges = packed[1], + .total_node_weight = packed[2], + .maximum_node_weight = packed[3], + .total_directed_edge_weight = packed[4], + .maximum_directed_edge_weight = packed[5], + .block_count = packed[6], + .absolute_bound = packed[7], + .wire_record_bytes = packed[8], + .csr_bytes = packed[9], + .partition_bytes = packed[10], + .serial_input_bytes = packed[11], + .complete_graph_bytes = packed[12], + .structural_validation_bytes = packed[13], + .base_memory_bytes = packed[14], + .flat_payload_elements = packed[15], + .reason = static_cast(packed[16]), + }; + if (!profile.safe()) { + abort_unsafe_serial_profile(communicator, rank, profile); + } + return profile; +} - G.build_from_metis_weighted( number_of_nodes, xadj, adjncy, vwgt, adjwgt); +void mpi_tools::collect_parallel_graph_to_checked_serial_graph( + MPI_Comm communicator, + PPartitionConfig const& config, + parallel_graph_access& distributed, + complete_graph_access& complete) { + auto const profile = preflight_serial_kernel(communicator, config, distributed); + mpi_tools_detail::observe_checked_serial_kernel_profile(profile); + collect_parallel_graph_to_local_graph_impl(communicator, config, distributed, + complete, true); +} - delete[] xadj; - delete[] adjncy; - delete[] vwgt; - delete[] adjwgt; +void collect_parallel_graph_to_local_graph_impl( + MPI_Comm communicator, + PPartitionConfig const&, + parallel_graph_access& distributed, + complete_graph_access& complete, + bool require_serial_kernel_structure) { + auto const communicator_view = mpi::communicator_view{communicator}; + mpi::require_live_intracommunicator( + communicator_view, + "complete graph collection requires a live intracommunicator"); + auto const rank = communicator_view.rank(); + auto const communicator_size = + static_cast(communicator_view.size()); + + static_cast(mpi::resolve_capacity_collectively( + packed_graph_capacity(distributed), communicator, communicator, + "complete graph collection")); + + auto global_nodes = std::uint64_t{}; + auto global_edges = std::uint64_t{}; + try { + global_nodes = mpi::agree_collectively( + static_cast(distributed.number_of_global_nodes()), + communicator_view, + "complete graph global node count differs across ranks"); + global_edges = mpi::agree_collectively( + static_cast(distributed.number_of_global_edges()), + communicator_view, + "complete graph global edge count differs across ranks"); + } catch (...) { + mpi::abort_on_exception(communicator, + "complete graph collection metadata failure"); + } + + auto received_nodes = + std::optional>{}; + auto received_edges = + std::optional>{}; + try { + auto records = pack_local_graph(distributed); + received_nodes.emplace(mpi::all_to_all_v( + make_root_exchange(std::move(records.nodes), communicator_size), + communicator_view)); + received_edges.emplace(mpi::all_to_all_v( + make_root_exchange(std::move(records.edges), communicator_size), + communicator_view)); + } catch (...) { + mpi::abort_on_exception(communicator, + "complete graph collection local failure"); + } + + auto payload_is_valid = rank != static_cast(ROOT); + if (rank == static_cast(ROOT)) { + try { + payload_is_valid = complete_graph_payload_is_valid( + *received_nodes, *received_edges, static_cast(global_nodes), + static_cast(global_edges), communicator_size, + require_serial_kernel_structure); + } catch (...) { + mpi::abort_on_exception( + communicator, "complete graph collection payload inspection failed"); + } + } + if (require_serial_kernel_structure) { + auto const local_valid = payload_is_valid ? 1 : 0; + auto all_valid = 0; + mpi::check_or_abort( + MPI_Allreduce(&local_valid, &all_valid, 1, MPI_INT, MPI_MIN, + communicator), + communicator, "MPI_Allreduce(serial kernel structure validation)"); + if (all_valid == 0) { + if (rank == static_cast(ROOT)) { + kahip::diagnostics::critical( + "ParHIP serial kernel structural validation failure: expected " + "loop-free reciprocal-undirected weighted adjacency"); + } + static_cast(MPI_Abort(communicator, EXIT_FAILURE)); + std::abort(); + } + } else { + try { + mpi::validate_collectively(payload_is_valid, communicator_view, + "complete graph collection payload is invalid"); + } catch (...) { + mpi::abort_on_exception(communicator, + "complete graph collection validation failure"); + } + } + + if (rank == static_cast(ROOT)) { + try { + construct_complete_graph(complete, *received_nodes, *received_edges, + static_cast(global_nodes), + static_cast(global_edges)); + } catch (...) { + mpi::abort_on_exception(communicator, + "complete graph collection construction failed"); + } + } } -void mpi_tools::alltoallv( void * sendbuf, - ULONG sendcounts[], ULONG displs[], - const MPI_Datatype & sendtype, void * recvbuf, - ULONG recvcounts[], ULONG rdispls[], - const MPI_Datatype & recvtype, MPI_Comm communicator ) { - - int rank, size; - MPI_Comm_rank( communicator, &rank); - MPI_Comm_size( communicator, &size); - - bool no_special_case = true; - for( int i = 0; i < size && no_special_case; i++) { - if( sendcounts[i] > std::numeric_limits< int >::max()) no_special_case = false; - if( recvcounts[i] > std::numeric_limits< int >::max()) no_special_case = false; - } - if( displs[size] > std::numeric_limits< int >::max()) no_special_case = false; - if( rdispls[size] > std::numeric_limits< int >::max()) no_special_case = false; - - if( no_special_case ) { - int sbktsize[size]; - int rbktsize[size]; - int sdispl[size+1]; - int rdispl[size+1]; - - for( int i = 0; i < size; i++) { - sbktsize[i] = sendcounts[i]; - rbktsize[i] = recvcounts[i]; - } - - for( int i = 0; i <= size; i++) { - sdispl[i] = displs[i]; - rdispl[i] = rdispls[i]; - } - - MPI_Alltoallv(sendbuf, sbktsize, sdispl, MPI_UNSIGNED_LONG_LONG, - recvbuf, rbktsize, rdispl, MPI_UNSIGNED_LONG_LONG, communicator); - } else { - if( rank == ROOT ) { std::cout << "special case all to all with counts > sizeof(int)! not tested yet!" << std::endl; exit(0);} - } +void mpi_tools::collect_parallel_graph_to_local_graph( + MPI_Comm communicator, + PPartitionConfig const& config, + parallel_graph_access& distributed, + complete_graph_access& complete) { + collect_parallel_graph_to_local_graph_impl(communicator, config, distributed, + complete, false); } +void mpi_tools::distribute_local_graph(MPI_Comm communicator, + PPartitionConfig&, + complete_graph_access& graph) { + auto owned_communicator = + mpi::communicator{mpi::communicator_view{communicator}}; + auto const collective = owned_communicator.view(); + auto const rank = collective.rank(); + + auto header = std::array{}; + if (rank == static_cast(ROOT)) { + try { + header = { + static_cast(graph.number_of_global_nodes()), + static_cast(graph.number_of_global_edges()), + static_cast(root_distribution_status(graph)), + }; + } catch (...) { + mpi::abort_on_exception( + collective.native_handle(), + "complete graph distribution root inspection failed"); + } + } + mpi::broadcast_fixed(std::span{header}, ROOT, collective, + "MPI_Bcast(complete graph header)"); + + auto const status = static_cast(header[2]); + if (status == distribution_status::incomplete_graph) { + mpi::abort_on_programming_error( + collective.native_handle(), + "complete graph distribution requires a complete valid root graph"); + } + if (status != distribution_status::valid) { + mpi::abort_on_capacity_failure( + collective.native_handle(), "complete graph distribution", + "serial graph representation exceeds int capacity"); + } + if (!std::in_range(header[0]) || + !std::in_range(header[1])) { + mpi::abort_on_capacity_failure(collective.native_handle(), + "complete graph distribution", + "graph counts exceed local size_t capacity"); + } + + auto const nodes = static_cast(header[0]); + auto const edges = static_cast(header[1]); + auto const payload_size = + checked_payload_size(nodes, edges, collective.native_handle()); + auto const xadj_offset = std::size_t{0}; + auto const adjncy_offset = nodes + std::size_t{1}; + auto const node_weight_offset = adjncy_offset + edges; + auto const edge_weight_offset = node_weight_offset + nodes; + + auto payload = std::vector{}; + try { + payload.resize(payload_size); + } catch (...) { + mpi::abort_on_exception(collective.native_handle(), + "complete graph distribution allocation failure"); + } + + if (rank == static_cast(ROOT)) { + for (std::size_t node = 0; node < nodes; ++node) { + auto const node_id = static_cast(node); + payload[xadj_offset + node] = + static_cast(graph.get_first_edge(node_id)); + payload[node_weight_offset + node] = + static_cast(graph.getNodeWeight(node_id)); + } + payload[xadj_offset + nodes] = static_cast(edges); + for (std::size_t edge = 0; edge < edges; ++edge) { + auto const edge_id = static_cast(edge); + payload[adjncy_offset + edge] = + static_cast(graph.getEdgeTarget(edge_id)); + payload[edge_weight_offset + edge] = + static_cast(graph.getEdgeWeight(edge_id)); + } + } + + mpi::broadcast_bounded(std::span{payload}, ROOT, collective, + "MPI_Bcast(complete graph payload)"); + + if (rank != static_cast(ROOT)) { + graph.build_from_metis_weighted( + static_cast(nodes), payload.data() + xadj_offset, + payload.data() + adjncy_offset, payload.data() + node_weight_offset, + payload.data() + edge_weight_offset); + } +} +} // namespace parhip diff --git a/parallel/parallel_src/lib/communication/mpi_tools.h b/parallel/parallel_src/lib/communication/mpi_tools.h index 53330def..61e82c5b 100644 --- a/parallel/parallel_src/lib/communication/mpi_tools.h +++ b/parallel/parallel_src/lib/communication/mpi_tools.h @@ -5,48 +5,198 @@ * Christian Schulz *****************************************************************************/ - - #ifndef MPI_TOOLS_HMESDXF2 #define MPI_TOOLS_HMESDXF2 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_adapter.h" #include "data_structure/parallel_graph_access.h" #include "partition_config.h" +#include "serial_kernel_profile.h" +namespace parhip { +namespace mpi_tools_detail { +struct complete_graph_node_record final { + std::uint64_t global_id; + std::uint64_t second_partition; + std::uint64_t weight; + std::uint64_t degree; + + auto operator==(complete_graph_node_record const&) const -> bool = default; +}; + +struct complete_graph_edge_record final { + std::uint64_t target_global_id; + std::uint64_t weight; + + auto operator==(complete_graph_edge_record const&) const -> bool = default; +}; + +static_assert(std::is_standard_layout_v); +static_assert(std::is_trivially_copyable_v); +static_assert(sizeof(complete_graph_node_record) == 4 * sizeof(std::uint64_t)); +static_assert(std::is_standard_layout_v); +static_assert(std::is_trivially_copyable_v); +static_assert(sizeof(complete_graph_edge_record) == 2 * sizeof(std::uint64_t)); +} // namespace mpi_tools_detail + +namespace mpi { +template <> +struct wire_members { + inline static constexpr auto value = std::tuple{ + &mpi_tools_detail::complete_graph_node_record::global_id, + &mpi_tools_detail::complete_graph_node_record::second_partition, + &mpi_tools_detail::complete_graph_node_record::weight, + &mpi_tools_detail::complete_graph_node_record::degree}; +}; + +template <> +struct wire_members { + inline static constexpr auto value = std::tuple{ + &mpi_tools_detail::complete_graph_edge_record::target_global_id, + &mpi_tools_detail::complete_graph_edge_record::weight}; +}; +} // namespace mpi class mpi_tools { -public: - mpi_tools(); - virtual ~mpi_tools(); - - void collect_and_write_labels( MPI_Comm communicator, PPartitionConfig & config, - parallel_graph_access & G); - - void collect_parallel_graph_to_local_graph( MPI_Comm communicator, - PPartitionConfig & config, - parallel_graph_access & G, - complete_graph_access & Q); - - // G is input (only on ROOT) - // G is output (on every other PE) - void distribute_local_graph( MPI_Comm communicator, PPartitionConfig & config, complete_graph_access & G); - - // alltoallv that can send more than int-count elements - void alltoallv( void * sendbuf, - ULONG sendcounts[], ULONG displs[], - const MPI_Datatype & sendtype, void * recvbuf, - ULONG recvcounts[], ULONG rdispls[], - const MPI_Datatype & recvtype ) { - alltoallv( sendbuf, sendcounts, displs, sendtype, recvbuf, recvcounts, rdispls, recvtype, MPI_COMM_WORLD); - }; - - void alltoallv( void * sendbuf, - ULONG sendcounts[], ULONG displs[], - const MPI_Datatype & sendtype, void * recvbuf, - ULONG recvcounts[], ULONG rdispls[], - const MPI_Datatype & recvtype, MPI_Comm communicator ); + public: + [[nodiscard]] auto preflight_serial_kernel( + MPI_Comm communicator, + PPartitionConfig const& config, + parallel_graph_access& graph) const + -> kahip::serial_kernel::serial_kernel_profile; + + void collect_parallel_graph_to_checked_serial_graph( + MPI_Comm communicator, + PPartitionConfig const& config, + parallel_graph_access& distributed, + complete_graph_access& complete); + + void collect_parallel_graph_to_local_graph(MPI_Comm communicator, + PPartitionConfig const& config, + parallel_graph_access& G, + complete_graph_access& Q); + // G is input (only on ROOT) + // G is output (on every other PE) + void distribute_local_graph(MPI_Comm communicator, + PPartitionConfig& config, + complete_graph_access& G); +}; + +namespace mpi { +template +struct mpi_packed_message { + std::vector packed_message; + std::vector offsets; + std::vector lengths; }; +template + requires std::ranges::forward_range> +auto pack_messages(Input const& messages) -> mpi_packed_message< + std::ranges::range_value_t>> { + using InnerRange = std::ranges::range_value_t; + using ElementType = std::ranges::range_value_t; + + // Flattening the container of containers using views::join + auto flattened_view = messages | std::ranges::views::join; + std::vector flattened_vector{flattened_view.begin(), + flattened_view.end()}; + + // Calculating lengths of the inner ranges + std::vector lengths; + lengths.reserve(std::ranges::distance(messages)); + for (auto const& inner : messages) { + lengths.push_back(static_cast(std::ranges::distance(inner))); + } + + // Calculating offsets using exclusive_scan + std::vector offsets(lengths.size()); + std::exclusive_scan(lengths.begin(), lengths.end(), offsets.begin(), + std::size_t{0}); + + return mpi_packed_message{flattened_vector, offsets, lengths}; +} + +template +auto unpack_messages(mpi_packed_message const& packed_message) + -> std::vector> { + auto const& [recv_buf, recv_displs, recv_counts] = packed_message; + std::size_t num_ranks = recv_counts.size(); + + // Ensure recv_displs and recv_counts have the same size + assert(recv_displs.size() == num_ranks); + + std::vector> result; + result.reserve(num_ranks); + + // Use std::transform to construct the sub-vectors + std::transform(recv_displs.begin(), recv_displs.end(), recv_counts.begin(), + std::back_inserter(result), + [&recv_buf](std::size_t displ, std::size_t count) { + auto const start = recv_buf.begin() + displ; + auto const end = start + count; + return std::vector(start, end); + }); + + return result; +} + +template +concept mpi_nested_range = requires(Input) { + requires std::ranges::forward_range; + requires std::ranges::forward_range>; + requires mpi_datatype< + std::ranges::range_value_t>>; +}; + +template +using mpi_alltoall_t = std::vector< + std::vector>>>; + +/** + * @brief Performs an MPI all-to-all communication operation, distributing + * data from all processes to all processes. + * + * This function packs messages from the input data structure, performs an + * MPI all-to-all communication, and then unpacks the received messages. + * + * @param sends A structure containing the data to be sent from each + * process. + * @param communicator The MPI communicator used for the all-to-all + * operation. + * @return A vector of vectors, where each inner vector contains the data + * received by a process from other processes. + * @throws std::runtime_error if there's an inconsistency in the send + * offsets/lengths or if the MPI operation fails. + */ +template +auto all_to_all(Input const& sends, MPI_Comm communicator) + -> mpi_alltoall_t { + using InnerRange = std::ranges::range_value_t; + using ElementType = std::ranges::range_value_t; + auto received = + all_to_all_v(segmented_buffer::from_segments(sends), + communicator_view{communicator}); + std::vector> result; + result.reserve(received.segment_count()); + for (std::size_t source = 0; source < received.segment_count(); ++source) { + auto const segment = received.segment(source); + result.emplace_back(segment.begin(), segment.end()); + } + return result; +} +} // namespace mpi +} // namespace parhip #endif /* end of include guard: MPI_TOOLS_HMESDXF2 */ diff --git a/parallel/parallel_src/lib/communication/mpi_trace.h b/parallel/parallel_src/lib/communication/mpi_trace.h new file mode 100644 index 00000000..0a85b8a3 --- /dev/null +++ b/parallel/parallel_src/lib/communication/mpi_trace.h @@ -0,0 +1,604 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_failure.h" +#include "communication/mpi_handles.h" + +#ifndef KAHIP_ENABLE_MPI_TRACE +#define KAHIP_ENABLE_MPI_TRACE 0 +#endif + +namespace parhip::mpi::trace { +inline constexpr std::string_view format_version = "kahip-mpi-trace-v3"; +inline constexpr std::string_view upstream_revision = + "5935f349f65f1788a9b68fcf6d853e698d86956d"; + +enum class stage : std::uint8_t { + graph_distribution_node, + graph_distribution_edge, + contraction_label, + quotient_node_weight, + quotient_edge, + projection_request, + projection_reply, + ghost_update, + block_propagation, + final_partition +}; + +enum class epoch : std::uint8_t { + input, + coarsening, + contraction, + initial_partition, + projection, + refinement, + final_partition +}; + +struct hierarchy_position { + std::uint32_t cycle; + std::uint32_t level; + epoch epoch_id; + std::uint32_t iteration; + std::uint32_t round; + + auto operator==(hierarchy_position const&) const -> bool = default; +}; + +struct semantic_actors { + int owner = -1; + int requester = -1; + int receiver = -1; + + auto operator==(semantic_actors const&) const -> bool = default; +}; + +inline constexpr auto all_stages = std::array{ + stage::graph_distribution_node, + stage::graph_distribution_edge, + stage::contraction_label, + stage::quotient_node_weight, + stage::quotient_edge, + stage::projection_request, + stage::projection_reply, + stage::ghost_update, + stage::block_propagation, + stage::final_partition}; + +struct record { + stage stage_id; + hierarchy_position hierarchy; + std::uint64_t global_id; + semantic_actors actors; + std::string semantic_key; + std::string payload; + + auto operator==(record const&) const -> bool = default; +}; + +[[nodiscard]] inline auto stage_name(stage value) -> std::string_view { + switch (value) { + case stage::graph_distribution_node: + return "graph-distribution-node"; + case stage::graph_distribution_edge: + return "graph-distribution-edge"; + case stage::contraction_label: + return "contraction-label"; + case stage::quotient_node_weight: + return "quotient-node-weight"; + case stage::quotient_edge: + return "quotient-edge"; + case stage::projection_request: + return "projection-request"; + case stage::projection_reply: + return "projection-reply"; + case stage::ghost_update: + return "ghost-update"; + case stage::block_propagation: + return "block-propagation"; + case stage::final_partition: + return "final-partition"; + } + throw std::logic_error{"unknown MPI trace stage"}; +} + +[[nodiscard]] inline auto epoch_name(epoch value) -> std::string_view { + switch (value) { + case epoch::input: + return "input"; + case epoch::coarsening: + return "coarsening"; + case epoch::contraction: + return "contraction"; + case epoch::initial_partition: + return "initial-partition"; + case epoch::projection: + return "projection"; + case epoch::refinement: + return "refinement"; + case epoch::final_partition: + return "final-partition"; + } + throw std::logic_error{"unknown MPI trace epoch"}; +} + +[[nodiscard]] inline auto rank_name(int rank) -> std::string { + return rank < 0 ? "-" : std::to_string(rank); +} + +[[nodiscard]] inline auto graph_distribution_node(hierarchy_position hierarchy, + std::uint64_t global_id, + int owner, + std::uint64_t weight) + -> record { + return {stage::graph_distribution_node, + hierarchy, + global_id, + {.owner = owner, .receiver = owner}, + "owner:" + std::to_string(owner), + "weight=" + std::to_string(weight)}; +} + +[[nodiscard]] inline auto graph_distribution_edge(hierarchy_position hierarchy, + std::uint64_t source, + int owner, + std::uint64_t target, + std::uint64_t weight) + -> record { + return {stage::graph_distribution_edge, + hierarchy, + source, + {.owner = owner, .receiver = owner}, + "target:" + std::to_string(target), + "weight=" + std::to_string(weight)}; +} + +[[nodiscard]] inline auto contraction_label(hierarchy_position hierarchy, + std::uint64_t global_id, + int owner, + std::uint64_t label, + std::uint64_t coarse_id) -> record { + return {stage::contraction_label, + hierarchy, + global_id, + {.owner = owner, .receiver = owner}, + "label:" + std::to_string(label), + "coarse=" + std::to_string(coarse_id)}; +} + +[[nodiscard]] inline auto quotient_node_weight(hierarchy_position hierarchy, + std::uint64_t global_id, + int owner, + std::uint64_t weight) -> record { + return {stage::quotient_node_weight, + hierarchy, + global_id, + {.owner = owner, .receiver = owner}, + "node", + "weight=" + std::to_string(weight)}; +} + +[[nodiscard]] inline auto quotient_edge(hierarchy_position hierarchy, + std::uint64_t source, + int owner, + std::uint64_t target, + std::uint64_t weight) -> record { + return {stage::quotient_edge, + hierarchy, + source, + {.owner = owner, .receiver = owner}, + "target:" + std::to_string(target), + "weight=" + std::to_string(weight)}; +} + +[[nodiscard]] inline auto projection_request(hierarchy_position hierarchy, + std::uint64_t request_id, + int requester, + int owner, + std::uint64_t coarse_id) -> record { + return {stage::projection_request, + hierarchy, + coarse_id, + {.owner = owner, .requester = requester, .receiver = owner}, + "request:" + std::to_string(request_id), + "requester=" + std::to_string(requester) + + " owner=" + std::to_string(owner)}; +} + +[[nodiscard]] inline auto projection_reply(hierarchy_position hierarchy, + std::uint64_t request_id, + int requester, + int owner, + std::uint64_t coarse_id, + std::uint64_t label) -> record { + return {stage::projection_reply, + hierarchy, + coarse_id, + {.owner = owner, .requester = requester, .receiver = requester}, + "request:" + std::to_string(request_id), + "requester=" + std::to_string(requester) + + " owner=" + std::to_string(owner) + + " label=" + std::to_string(label)}; +} + +[[nodiscard]] inline auto ghost_update(hierarchy_position hierarchy, + std::uint64_t global_id, + int owner, + int receiver, + std::uint64_t label) -> record { + return {stage::ghost_update, + hierarchy, + global_id, + {.owner = owner, .receiver = receiver}, + "label", + "label=" + std::to_string(label)}; +} + +[[nodiscard]] inline auto block_propagation(hierarchy_position hierarchy, + std::uint64_t global_id, + int owner, + int receiver, + std::uint64_t block) -> record { + return {stage::block_propagation, + hierarchy, + global_id, + {.owner = owner, .receiver = receiver}, + "block", + "block=" + std::to_string(block)}; +} + +[[nodiscard]] inline auto final_partition(hierarchy_position hierarchy, + std::uint64_t global_id, + int owner, + std::uint64_t block) -> record { + return {stage::final_partition, + hierarchy, + global_id, + {.owner = owner, .receiver = owner}, + "partition", + "block=" + std::to_string(block)}; +} + +[[nodiscard]] inline auto canonical_text(std::span input) + -> std::string { + auto records = std::vector{input.begin(), input.end()}; + std::ranges::sort(records, {}, [](record const& value) { + return std::tie(value.stage_id, + value.hierarchy.cycle, + value.hierarchy.level, + value.hierarchy.epoch_id, + value.hierarchy.iteration, + value.hierarchy.round, + value.global_id, + value.actors.owner, + value.actors.requester, + value.actors.receiver, + value.semantic_key, + value.payload); + }); + + auto output = std::string{format_version} + " upstream=" + + std::string{upstream_revision} + "\n"; + for (auto const& value : records) { + output += std::string{stage_name(value.stage_id)} + + " cycle=" + std::to_string(value.hierarchy.cycle) + + " level=" + std::to_string(value.hierarchy.level) + + " epoch=" + std::string{epoch_name(value.hierarchy.epoch_id)} + + " iteration=" + std::to_string(value.hierarchy.iteration) + + " round=" + std::to_string(value.hierarchy.round) + + " global=" + std::to_string(value.global_id) + + " owner=" + rank_name(value.actors.owner) + + " requester=" + rank_name(value.actors.requester) + + " receiver=" + rank_name(value.actors.receiver) + + " key=" + value.semantic_key; + if (!value.payload.empty()) { + output += " " + value.payload; + } + output += '\n'; + } + return output; +} + +[[nodiscard]] inline auto canonical_text(std::vector const& input) + -> std::string { + return canonical_text(std::span{input}); +} + +[[nodiscard]] inline auto sanitize_run_id(std::string_view run_id) + -> std::string { + auto result = std::string{run_id}; + for (auto& character : result) { + auto const ascii_alphanumeric = + (character >= 'a' && character <= 'z') || + (character >= 'A' && character <= 'Z') || + (character >= '0' && character <= '9'); + if (!ascii_alphanumeric && character != '-' && character != '_') { + character = '_'; + } + } + return result; +} + +[[nodiscard]] inline auto stable_run_id_hash(std::string_view run_id) + -> std::uint64_t { + constexpr std::uint64_t offset_basis = 14695981039346656037ULL; + constexpr std::uint64_t prime = 1099511628211ULL; + auto hash = offset_basis; + for (auto const character : run_id) { + hash ^= static_cast(character); + hash *= prime; + } + return hash; +} + +[[nodiscard]] inline auto hexadecimal(std::uint64_t value) -> std::string { + constexpr auto digits = std::string_view{"0123456789abcdef"}; + auto result = std::string(16, '0'); + for (auto index = result.size(); index > 0; --index) { + auto const digit = static_cast(value & 0xfU); + result[index - 1] = digits[digit]; + value >>= 4U; + } + return result; +} + +[[nodiscard]] inline auto run_id_filename_component(std::string_view run_id) + -> std::string { + return sanitize_run_id(run_id) + "-" + + hexadecimal(stable_run_id_hash(run_id)); +} + +[[nodiscard]] inline auto rank_file_path(std::string_view base_path, + std::string_view run_id, + int rank) -> std::string { + if (run_id.empty()) { + throw std::invalid_argument{ + "MPI trace filename requires a collectively resolved run ID"}; + } + return std::string{base_path} + ".run-" + + run_id_filename_component(run_id) + ".rank" + + std::to_string(rank) + ".trace"; +} + +#if KAHIP_ENABLE_MPI_TRACE +namespace detail { +inline std::vector records; +inline bool active = std::getenv("KAHIP_MPI_TRACE_PATH") != nullptr; +inline hierarchy_position hierarchy{ + .cycle = 0, + .level = 0, + .epoch_id = epoch::input, + .iteration = 0, + .round = 0}; + +[[nodiscard]] inline auto automatic_run_id() -> std::string { + auto entropy = std::random_device{}; + auto first = (static_cast(entropy()) << 32U) | + static_cast(entropy()); + auto second = (static_cast(entropy()) << 32U) | + static_cast(entropy()); + first ^= static_cast( + std::chrono::system_clock::now().time_since_epoch().count()); + second ^= static_cast( + std::chrono::steady_clock::now().time_since_epoch().count()); + return "auto-" + hexadecimal(first) + hexadecimal(second); +} + +[[nodiscard]] inline auto broadcast_string(MPI_Comm communicator, + int rank, + std::string root_value) + -> std::string { + auto length = rank == 0 + ? static_cast(root_value.size()) + : 0ULL; + ::parhip::mpi::check_or_abort( + MPI_Bcast(&length, 1, MPI_UNSIGNED_LONG_LONG, 0, communicator), + communicator, "MPI_Bcast(trace string length)"); + if (length > + static_cast(std::numeric_limits::max())) { + throw std::runtime_error{"MPI trace run ID is too long"}; + } + + auto bytes = std::vector(static_cast(length)); + if (rank == 0) { + std::ranges::copy(root_value, bytes.begin()); + } + ::parhip::mpi::check_or_abort( + MPI_Bcast(bytes.data(), static_cast(length), MPI_CHAR, 0, + communicator), + communicator, "MPI_Bcast(trace string payload)"); + return {bytes.begin(), bytes.end()}; +} + +[[nodiscard]] inline auto resolve_base_path_collectively( + MPI_Comm communicator, + char const* local_base_path) -> std::optional { + int rank = 0; + ::parhip::mpi::check_or_abort(MPI_Comm_rank(communicator, &rank), + communicator, "MPI_Comm_rank(trace path)"); + + auto const local_present = local_base_path == nullptr ? 0 : 1; + auto root_present = rank == 0 ? local_present : 0; + ::parhip::mpi::check_or_abort( + MPI_Bcast(&root_present, 1, MPI_INT, 0, communicator), communicator, + "MPI_Bcast(trace path presence)"); + auto const root_path = broadcast_string( + communicator, rank, + rank == 0 && local_base_path != nullptr ? local_base_path : ""); + auto const local_mismatch = + local_present != root_present || + (local_present != 0 && std::string_view{local_base_path} != root_path); + auto mismatch = local_mismatch ? 1 : 0; + auto any_mismatch = 0; + ::parhip::mpi::check_or_abort( + MPI_Allreduce(&mismatch, &any_mismatch, 1, MPI_INT, MPI_MAX, + communicator), + communicator, "MPI_Allreduce(trace path agreement)"); + if (any_mismatch != 0) { + throw std::runtime_error{ + "MPI trace path differs across communicator ranks"}; + } + if (root_present == 0) { + return std::nullopt; + } + return root_path; +} +} // namespace detail + +[[nodiscard]] inline auto requested_run_id() -> std::optional { + constexpr auto variables = std::array{ + "KAHIP_MPI_TRACE_RUN_ID", + "SLURM_JOB_ID", + "PBS_JOBID", + "LSB_JOBID", + "PMI_JOBID", + "OMPI_MCA_orte_ess_jobid"}; + for (auto const* variable : variables) { + auto const* value = std::getenv(variable); + if (value != nullptr && *value != '\0') { + return std::string{value}; + } + } + return std::nullopt; +} + +[[nodiscard]] inline auto resolve_run_id_collectively( + MPI_Comm communicator, + std::optional local_run_id) -> std::string { + if (local_run_id && local_run_id->empty()) { + local_run_id.reset(); + } + + int rank = 0; + ::parhip::mpi::check_or_abort(MPI_Comm_rank(communicator, &rank), + communicator, + "MPI_Comm_rank(trace run ID)"); + auto root_run_id = detail::broadcast_string( + communicator, rank, + rank == 0 ? local_run_id.value_or(std::string{}) : std::string{}); + auto const local_mismatch = + local_run_id ? *local_run_id != root_run_id : !root_run_id.empty(); + auto mismatch = local_mismatch ? 1 : 0; + auto any_mismatch = 0; + ::parhip::mpi::check_or_abort( + MPI_Allreduce(&mismatch, &any_mismatch, 1, MPI_INT, MPI_MAX, + communicator), + communicator, "MPI_Allreduce(trace run-ID agreement)"); + if (any_mismatch != 0) { + throw std::runtime_error{ + "MPI trace run ID differs across communicator ranks"}; + } + if (!root_run_id.empty()) { + return root_run_id; + } + + return detail::broadcast_string( + communicator, rank, rank == 0 ? detail::automatic_run_id() : ""); +} + +inline void set_active(bool active) { detail::active = active; } +inline void set_hierarchy(hierarchy_position hierarchy) { + detail::hierarchy = hierarchy; +} +inline void set_iteration(std::uint32_t iteration) { + detail::hierarchy.iteration = iteration; +} +[[nodiscard]] inline auto current_hierarchy() -> hierarchy_position { + return detail::hierarchy; +} +[[nodiscard]] inline auto current_hierarchy_with_round(std::uint32_t round) + -> hierarchy_position { + auto hierarchy = current_hierarchy(); + hierarchy.round = round; + return hierarchy; +} +inline void reset() { detail::records.clear(); } +[[nodiscard]] inline auto snapshot() -> std::vector { + return detail::records; +} +inline void append(record value) { + if (detail::active) { + detail::records.push_back(std::move(value)); + } +} +inline void write_rank_file_if_requested(MPI_Comm communicator) { + auto owned_communicator = ::parhip::mpi::communicator{ + ::parhip::mpi::communicator_view{communicator}}; + auto const collective_communicator = owned_communicator.view(); + auto const base_path = detail::resolve_base_path_collectively( + collective_communicator.native_handle(), + std::getenv("KAHIP_MPI_TRACE_PATH")); + if (!base_path) { + return; + } + auto const rank = collective_communicator.rank(); + auto const run_id = resolve_run_id_collectively( + collective_communicator.native_handle(), requested_run_id()); + auto const path = rank_file_path(*base_path, run_id, rank); + auto output = std::ofstream{path, std::ios::binary | std::ios::trunc}; + if (!output) { + throw std::runtime_error{"MPI trace could not open " + path}; + } + output << canonical_text(detail::records); + if (!output) { + throw std::runtime_error{"MPI trace could not write " + path}; + } +} +#else +inline void set_active(bool) noexcept {} +inline void reset() noexcept {} +[[nodiscard]] inline auto snapshot() -> std::vector { return {}; } +inline void append(record) noexcept {} +inline void write_rank_file_if_requested(MPI_Comm) noexcept {} +#endif +} // namespace parhip::mpi::trace + +#if KAHIP_ENABLE_MPI_TRACE +#define KAHIP_MPI_TRACE(record_expression) \ + do { \ + ::parhip::mpi::trace::append((record_expression)); \ + } while (false) +#define KAHIP_MPI_TRACE_SET_HIERARCHY(cycle_value, level_value, epoch_value) \ + do { \ + ::parhip::mpi::trace::set_hierarchy( \ + {.cycle = static_cast(cycle_value), \ + .level = static_cast(level_value), \ + .epoch_id = (epoch_value), \ + .iteration = 0, \ + .round = 0}); \ + } while (false) +#define KAHIP_MPI_TRACE_SET_ITERATION(iteration_value) \ + do { \ + ::parhip::mpi::trace::set_iteration( \ + static_cast(iteration_value)); \ + } while (false) +#else +#define KAHIP_MPI_TRACE(record_expression) \ + do { \ + } while (false) +#define KAHIP_MPI_TRACE_SET_HIERARCHY(cycle_value, level_value, epoch_value) \ + do { \ + } while (false) +#define KAHIP_MPI_TRACE_SET_ITERATION(iteration_value) \ + do { \ + } while (false) +#endif diff --git a/parallel/parallel_src/lib/communication/mpi_types.h b/parallel/parallel_src/lib/communication/mpi_types.h new file mode 100644 index 00000000..a19a7a41 --- /dev/null +++ b/parallel/parallel_src/lib/communication/mpi_types.h @@ -0,0 +1,269 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "communication/implicit_lifetime.h" +#include "communication/mpi_error.h" +#include "communication/mpi_failure.h" + +namespace parhip::mpi { +namespace detail { +using native_mpi_types = std::tuple< + char, + wchar_t, + signed char, + unsigned char, + short, + unsigned short, + int, + unsigned int, + long, + unsigned long, + long long, + unsigned long long, + float, + double, + long double, + bool, + std::complex, + std::complex, + std::complex>; + +template +using unqualified_t = std::remove_cv_t>; + +template +struct tuple_contains; + +template +struct tuple_contains> + : std::bool_constant<(std::is_same_v || ...)> {}; + +template +inline constexpr bool tuple_contains_v = + tuple_contains>::value; + +template +struct tuple_index; + +template +struct tuple_index> + : std::integral_constant {}; + +template +struct tuple_index> + : std::integral_constant< + std::size_t, + 1 + tuple_index>::value> {}; + +template +inline constexpr std::size_t tuple_index_v = + tuple_index>::value; + +template +inline constexpr bool is_native_mpi_type = + tuple_contains_v, native_mpi_types>; + +inline auto const native_mpi_handles = std::array{ + MPI_CHAR, + MPI_WCHAR, + MPI_SIGNED_CHAR, + MPI_UNSIGNED_CHAR, + MPI_SHORT, + MPI_UNSIGNED_SHORT, + MPI_INT, + MPI_UNSIGNED, + MPI_LONG, + MPI_UNSIGNED_LONG, + MPI_LONG_LONG_INT, + MPI_UNSIGNED_LONG_LONG, + MPI_FLOAT, + MPI_DOUBLE, + MPI_LONG_DOUBLE, + MPI_CXX_BOOL, + MPI_CXX_FLOAT_COMPLEX, + MPI_CXX_DOUBLE_COMPLEX, + MPI_CXX_LONG_DOUBLE_COMPLEX}; + +static_assert(std::tuple_size_v == + std::tuple_size_v>, + "native MPI types and handles must remain aligned"); + +template + requires is_native_mpi_type +auto native_mpi_handle() noexcept -> MPI_Datatype { + constexpr auto index = + tuple_index_v, native_mpi_types>; + return native_mpi_handles[index]; +} + +template +struct tuple_members_are_native; + +template +struct tuple_members_are_native> + : std::bool_constant< + (is_native_mpi_type().* + std::declval())> && + ...)> {}; + +template +inline constexpr bool tuple_members_are_native_v = + tuple_members_are_native>::value; + +template +struct aligned_object_delete { + void operator()(T* object) const noexcept { + ::operator delete(object, std::align_val_t{alignof(T)}); + } +}; +} // namespace detail + +template +concept mpi_native_datatype = detail::is_native_mpi_type; + +template +struct wire_members; + +template +concept mpi_wire_datatype = + std::is_standard_layout_v> && + std::is_trivially_copyable_v> && requires { + requires detail::is_implicit_lifetime_v>; + wire_members>::value; + requires detail::tuple_members_are_native_v< + detail::unqualified_t, + decltype(wire_members>::value)>; + }; + +template +concept mpi_datatype = mpi_native_datatype || mpi_wire_datatype; + +class datatype { +public: + ~datatype() noexcept; + + datatype(datatype const&) = delete; + auto operator=(datatype const&) -> datatype& = delete; + datatype(datatype&& other) noexcept; + auto operator=(datatype&& other) noexcept -> datatype&; + + [[nodiscard]] static auto borrowed( + MPI_Datatype handle, + MPI_Comm failure_communicator = MPI_COMM_WORLD) noexcept + -> datatype { + return datatype{handle, false, failure_communicator}; + } + [[nodiscard]] static auto owned( + MPI_Datatype handle, + MPI_Comm failure_communicator = MPI_COMM_WORLD) noexcept -> datatype { + return datatype{handle, true, failure_communicator}; + } + + [[nodiscard]] auto native_handle() const noexcept -> MPI_Datatype { + return handle_; + } + [[nodiscard]] auto owns_handle() const noexcept -> bool { return owns_; } + +private: + explicit datatype(MPI_Datatype handle, + bool owns, + MPI_Comm failure_communicator) noexcept + : handle_(handle), + owns_(owns), + failure_communicator_(failure_communicator) {} + void reset() noexcept; + + MPI_Datatype handle_ = MPI_DATATYPE_NULL; + bool owns_ = false; + MPI_Comm failure_communicator_ = MPI_COMM_WORLD; +}; + +template +[[nodiscard]] auto make_mpi_datatype( + MPI_Comm failure_communicator = MPI_COMM_WORLD) -> datatype { + using value_type = detail::unqualified_t; + if constexpr (mpi_native_datatype) { + return datatype::borrowed( + detail::native_mpi_handle(), failure_communicator); + } else { + auto sample = + std::unique_ptr>{ + static_cast(::operator new( + sizeof(value_type), std::align_val_t{alignof(value_type)}))}; + MPI_Aint sample_address = 0; + check_or_abort(MPI_Get_address(sample.get(), &sample_address), + failure_communicator, + "MPI_Get_address(wire record)"); + + std::vector block_lengths; + std::vector offsets; + std::vector member_types; + constexpr auto member_count = std::tuple_size_v::value)>>; + block_lengths.reserve(member_count); + offsets.reserve(member_count); + member_types.reserve(member_count); + + auto append_member = [&](auto member) { + using member_type = + detail::unqualified_t*member)>; + static_assert(mpi_native_datatype, + "wire record members must use native MPI types"); + MPI_Aint member_address = 0; + check_or_abort( + MPI_Get_address(std::addressof(sample.get()->*member), + &member_address), + failure_communicator, + "MPI_Get_address(wire member)"); + block_lengths.push_back(1); + offsets.push_back(member_address - sample_address); + member_types.push_back(detail::native_mpi_handle()); + }; + std::apply( + [&](auto... members) { (append_member(members), ...); }, + wire_members::value); + + MPI_Datatype structure = MPI_DATATYPE_NULL; + check_or_abort(MPI_Type_create_struct( + static_cast(block_lengths.size()), + block_lengths.data(), + offsets.data(), + member_types.data(), + &structure), + failure_communicator, + "MPI_Type_create_struct"); + + MPI_Datatype resized = MPI_DATATYPE_NULL; + check_or_abort(MPI_Type_create_resized( + structure, + 0, + static_cast(sizeof(value_type)), + &resized), + failure_communicator, + "MPI_Type_create_resized"); + check_or_abort(MPI_Type_free(&structure), + failure_communicator, + "MPI_Type_free(wire structure)"); + check_or_abort(MPI_Type_commit(&resized), + failure_communicator, + "MPI_Type_commit"); + return datatype::owned(resized, failure_communicator); + } +} + +template +[[nodiscard]] auto get_mpi_datatype() noexcept -> MPI_Datatype { + return detail::native_mpi_handle(); +} +} // namespace parhip::mpi diff --git a/parallel/parallel_src/lib/communication/segmented_buffer.h b/parallel/parallel_src/lib/communication/segmented_buffer.h new file mode 100644 index 00000000..eaf7dba6 --- /dev/null +++ b/parallel/parallel_src/lib/communication/segmented_buffer.h @@ -0,0 +1,210 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "communication/implicit_lifetime.h" + +namespace parhip::mpi { +namespace detail { +template +class lifetime_storage { +public: + explicit lifetime_storage(std::size_t size) + requires is_implicit_lifetime_v + : size_(size) { + static_assert(std::is_trivially_copyable_v); + if (size_ == 0) { + return; + } + if (size_ > std::numeric_limits::max() / sizeof(T)) { + throw std::length_error{"segmented buffer storage is too large"}; + } + allocation_ = + ::operator new(size_ * sizeof(T), std::align_val_t{alignof(T)}); + data_ = static_cast(allocation_); + } + + ~lifetime_storage() noexcept { reset(); } + + lifetime_storage(lifetime_storage const&) = delete; + auto operator=(lifetime_storage const&) -> lifetime_storage& = delete; + + lifetime_storage(lifetime_storage&& other) noexcept + : allocation_(std::exchange(other.allocation_, nullptr)), + data_(std::exchange(other.data_, nullptr)), + size_(std::exchange(other.size_, 0)) {} + + auto operator=(lifetime_storage&& other) noexcept -> lifetime_storage& { + if (this != &other) { + reset(); + allocation_ = std::exchange(other.allocation_, nullptr); + data_ = std::exchange(other.data_, nullptr); + size_ = std::exchange(other.size_, 0); + } + return *this; + } + + [[nodiscard]] auto data() noexcept -> T* { return data_; } + [[nodiscard]] auto data() const noexcept -> T const* { return data_; } + [[nodiscard]] auto size() const noexcept -> std::size_t { return size_; } + +private: + void reset() noexcept { + if (allocation_ != nullptr) { + ::operator delete(allocation_, std::align_val_t{alignof(T)}); + } + allocation_ = nullptr; + data_ = nullptr; + size_ = 0; + } + + void* allocation_ = nullptr; + T* data_ = nullptr; + std::size_t size_ = 0; +}; +} // namespace detail + +template +class segmented_buffer { +public: + segmented_buffer() = default; + + segmented_buffer(segmented_buffer const&) = delete; + auto operator=(segmented_buffer const&) -> segmented_buffer& = delete; + segmented_buffer(segmented_buffer&&) noexcept = default; + auto operator=(segmented_buffer&&) noexcept -> segmented_buffer& = default; + + segmented_buffer(std::vector storage, + std::vector counts, + std::vector offsets) + : storage_(std::in_place_type>, std::move(storage)), + counts_(std::move(counts)), + offsets_(std::move(offsets)) {} + + [[nodiscard]] static auto uninitialized( + std::size_t storage_size, + std::vector counts, + std::vector offsets) -> segmented_buffer + requires std::is_trivially_copyable_v && + detail::is_implicit_lifetime_v + { + return segmented_buffer{uninitialized_tag{}, + storage_size, + std::move(counts), + std::move(offsets)}; + } + + template + requires std::ranges::forward_range> && + std::convertible_to< + std::ranges::range_value_t>, + T> + [[nodiscard]] static auto from_segments(Segments const& segments) + -> segmented_buffer { + std::vector storage; + std::vector counts; + std::vector offsets; + counts.reserve(static_cast(std::ranges::distance(segments))); + offsets.reserve(counts.capacity()); + + for (auto const& segment : segments) { + offsets.push_back(storage.size()); + counts.push_back( + static_cast(std::ranges::distance(segment))); + storage.insert(storage.end(), std::ranges::begin(segment), + std::ranges::end(segment)); + } + return segmented_buffer{ + std::move(storage), std::move(counts), std::move(offsets)}; + } + + [[nodiscard]] auto storage() noexcept -> std::span { + return std::visit( + [](auto& owner) -> std::span { + return {owner.data(), owner.size()}; + }, + storage_); + } + [[nodiscard]] auto storage() const noexcept -> std::span { + return std::visit( + [](auto const& owner) -> std::span { + return {owner.data(), owner.size()}; + }, + storage_); + } + [[nodiscard]] auto counts() const noexcept + -> std::vector const& { + return counts_; + } + [[nodiscard]] auto offsets() const noexcept + -> std::vector const& { + return offsets_; + } + [[nodiscard]] auto segment_count() const noexcept -> std::size_t { + return counts_.size(); + } + + [[nodiscard]] auto segment(std::size_t index) -> std::span { + validate_segment(index); + return storage().subspan(offsets_[index], counts_[index]); + } + [[nodiscard]] auto segment(std::size_t index) const -> std::span { + validate_segment(index); + return storage().subspan(offsets_[index], counts_[index]); + } + + [[nodiscard]] auto has_canonical_layout(std::size_t expected_segments) const + noexcept -> bool { + if (counts_.size() != expected_segments || + offsets_.size() != expected_segments) { + return false; + } + + std::size_t expected_offset = 0; + for (std::size_t index = 0; index < expected_segments; ++index) { + if (offsets_[index] != expected_offset || + counts_[index] > + std::numeric_limits::max() - expected_offset) { + return false; + } + expected_offset += counts_[index]; + } + return expected_offset == storage().size(); + } + +private: + struct uninitialized_tag {}; + + segmented_buffer(uninitialized_tag, + std::size_t storage_size, + std::vector counts, + std::vector offsets) + : storage_(std::in_place_type>, + storage_size), + counts_(std::move(counts)), + offsets_(std::move(offsets)) {} + + void validate_segment(std::size_t index) const { + if (index >= counts_.size() || index >= offsets_.size() || + offsets_[index] > storage().size() || + counts_[index] > storage().size() - offsets_[index]) { + throw std::out_of_range{"invalid segmented buffer segment"}; + } + } + + std::variant, detail::lifetime_storage> storage_; + std::vector counts_; + std::vector offsets_; +}; +} // namespace parhip::mpi diff --git a/parallel/parallel_src/lib/communication/serial_kernel_profile_observer.h b/parallel/parallel_src/lib/communication/serial_kernel_profile_observer.h new file mode 100644 index 00000000..d7841437 --- /dev/null +++ b/parallel/parallel_src/lib/communication/serial_kernel_profile_observer.h @@ -0,0 +1,41 @@ +/****************************************************************************** + * serial_kernel_profile_observer.h + * + * Test-only observation seam for the checked serial-kernel handoff. + *****************************************************************************/ + +#ifndef SERIAL_KERNEL_PROFILE_OBSERVER_H +#define SERIAL_KERNEL_PROFILE_OBSERVER_H + +#include "serial_kernel_profile.h" + +namespace parhip::mpi_tools_detail { +using serial_kernel_profile_observer_callback = void (*)( + void*, kahip::serial_kernel::serial_kernel_profile const&) noexcept; + +// This private observer is deliberately process-sequential and non-reentrant. +// Its sole storage is defined in mpi_tools.cpp. The caller and observer scope +// must resolve to the same linked library image; the public C-call test uses +// that exact parhip_interface linkage and does not mix in parallel separately. +class scoped_serial_kernel_profile_observer final { + public: + explicit scoped_serial_kernel_profile_observer( + serial_kernel_profile_observer_callback callback, + void* context) noexcept; + ~scoped_serial_kernel_profile_observer() noexcept; + + scoped_serial_kernel_profile_observer( + scoped_serial_kernel_profile_observer const&) = delete; + auto operator=(scoped_serial_kernel_profile_observer const&) + -> scoped_serial_kernel_profile_observer& = delete; + + private: + serial_kernel_profile_observer_callback previous_{}; + void* previous_context_{}; +}; + +void observe_checked_serial_kernel_profile( + kahip::serial_kernel::serial_kernel_profile const& profile) noexcept; +} // namespace parhip::mpi_tools_detail + +#endif diff --git a/parallel/parallel_src/lib/data_structure/balance_management.cpp b/parallel/parallel_src/lib/data_structure/balance_management.cpp index fa67585e..15425d9c 100644 --- a/parallel/parallel_src/lib/data_structure/balance_management.cpp +++ b/parallel/parallel_src/lib/data_structure/balance_management.cpp @@ -7,7 +7,7 @@ #include "balance_management.h" #include "data_structure/parallel_graph_access.h" - +namespace parhip { balance_management::balance_management( parallel_graph_access * G, NodeID total_num_labels ) : m_G ( G ), m_total_num_labels ( total_num_labels ) { @@ -16,5 +16,5 @@ balance_management::balance_management( parallel_graph_access * G, NodeID total_ balance_management::~balance_management() { } - +} diff --git a/parallel/parallel_src/lib/data_structure/balance_management.h b/parallel/parallel_src/lib/data_structure/balance_management.h index 2454e959..f30c32aa 100644 --- a/parallel/parallel_src/lib/data_structure/balance_management.h +++ b/parallel/parallel_src/lib/data_structure/balance_management.h @@ -15,27 +15,27 @@ /* This class gives you the amount of weight that is currently in a block. * The information can be inaccurate but correct after a call of * update_block_sizes_globally. */ - +namespace parhip { class parallel_graph_access; class balance_management { public: - balance_management( parallel_graph_access * G, NodeID total_num_labels); - virtual ~balance_management(); + balance_management( parallel_graph_access * G, NodeID total_num_labels); + virtual ~balance_management(); - virtual NodeWeight getBlockSize( PartitionID block ) = 0; - virtual void setBlockSize( PartitionID block, NodeWeight block_size ) = 0; - virtual void update_non_contained_block_balance( PartitionID from, PartitionID to, NodeWeight node_weight) = 0; + virtual NodeWeight getBlockSize( PartitionID block ) = 0; + virtual void setBlockSize( PartitionID block, NodeWeight block_size ) = 0; + virtual void update_non_contained_block_balance( PartitionID from, PartitionID to, NodeWeight node_weight) = 0; - // init local and total block sizes - virtual void init() = 0; - virtual void update() = 0; + // init local and total block sizes + virtual void init() = 0; + virtual void update() = 0; protected: - balance_management() {}; - parallel_graph_access * m_G; - NodeID m_total_num_labels; + balance_management() {}; + parallel_graph_access * m_G; + NodeID m_total_num_labels; }; - +} #endif /* end of include guard: BALANCE_MANAGEMENT_NJRUTX5K */ diff --git a/parallel/parallel_src/lib/data_structure/balance_management_coarsening.cpp b/parallel/parallel_src/lib/data_structure/balance_management_coarsening.cpp index a74cf877..a0121991 100644 --- a/parallel/parallel_src/lib/data_structure/balance_management_coarsening.cpp +++ b/parallel/parallel_src/lib/data_structure/balance_management_coarsening.cpp @@ -8,35 +8,36 @@ #include #include "balance_management_coarsening.h" #include "data_structure/parallel_graph_access.h" - -balance_management_coarsening::balance_management_coarsening(parallel_graph_access * G, PartitionID total_num_labels) +namespace parhip { +balance_management_coarsening::balance_management_coarsening(parallel_graph_access * G, PartitionID total_num_labels) : balance_management( G, total_num_labels) { - init(); + init(); } balance_management_coarsening::~balance_management_coarsening() { - + } void balance_management_coarsening::init( ) { - forall_local_nodes((*m_G), node) { - PartitionID label = m_G->getNodeLabel(node); - if( m_fuzzy_block_weights.find(label) == m_fuzzy_block_weights.end() ) { - m_fuzzy_block_weights[label] = 0; - } + forall_local_nodes((*m_G), node) { + PartitionID label = m_G->getNodeLabel(node); + if( m_fuzzy_block_weights.find(label) == m_fuzzy_block_weights.end() ) { + m_fuzzy_block_weights[label] = 0; + } - m_fuzzy_block_weights[label] += m_G->getNodeWeight(node); - } endfor + m_fuzzy_block_weights[label] += m_G->getNodeWeight(node); + } endfor - forall_ghost_nodes((*m_G),node) { - PartitionID label = m_G->getNodeLabel(node); - if( m_fuzzy_block_weights.find(label) == m_fuzzy_block_weights.end() ) { - m_fuzzy_block_weights[label] = 0; - } - m_fuzzy_block_weights[label] += m_G->getNodeWeight(node); - } endfor + forall_ghost_nodes((*m_G),node) { + PartitionID label = m_G->getNodeLabel(node); + if( m_fuzzy_block_weights.find(label) == m_fuzzy_block_weights.end() ) { + m_fuzzy_block_weights[label] = 0; + } + m_fuzzy_block_weights[label] += m_G->getNodeWeight(node); + } endfor } void balance_management_coarsening::update( ) { } +} \ No newline at end of file diff --git a/parallel/parallel_src/lib/data_structure/balance_management_coarsening.h b/parallel/parallel_src/lib/data_structure/balance_management_coarsening.h index cdc6aae1..56869a54 100644 --- a/parallel/parallel_src/lib/data_structure/balance_management_coarsening.h +++ b/parallel/parallel_src/lib/data_structure/balance_management_coarsening.h @@ -9,7 +9,7 @@ #define BALANCE_MANAGEMENT_COARSENING_TS6EZN5A #include "balance_management.h" - +namespace parhip { class parallel_graph_access; class balance_management_coarsening : public balance_management { @@ -57,5 +57,5 @@ void balance_management_coarsening::update_non_contained_block_balance( Partitio m_fuzzy_block_weights[to] += node_weight; } } - +} #endif /* end of include guard: BALANCE_MANAGEMENT_COARSENING_TS6EZN5A */ diff --git a/parallel/parallel_src/lib/data_structure/balance_management_refinement.cpp b/parallel/parallel_src/lib/data_structure/balance_management_refinement.cpp index 18d3c515..3922f17f 100644 --- a/parallel/parallel_src/lib/data_structure/balance_management_refinement.cpp +++ b/parallel/parallel_src/lib/data_structure/balance_management_refinement.cpp @@ -6,35 +6,221 @@ *****************************************************************************/ #include "balance_management_refinement.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_adapter.h" +#include "communication/mpi_fixed_reduction.h" #include "parallel_graph_access.h" -balance_management_refinement::balance_management_refinement(parallel_graph_access * G, PartitionID total_num_labels) -: balance_management( G, total_num_labels) { - m_total_block_weights.resize( total_num_labels ); - m_local_block_weights.resize( total_num_labels ); +namespace parhip { +namespace { +[[nodiscard]] constexpr auto checked_add(NodeWeight& accumulator, + NodeWeight value) noexcept -> bool { + if (value > std::numeric_limits::max() - accumulator) { + return false; + } + accumulator += value; + return true; +} + +[[nodiscard]] auto validated_block_count( + PartitionID local_count, + mpi::communicator_view communicator) noexcept -> std::size_t { + static_assert(sizeof(PartitionID) <= sizeof(std::uint64_t)); + auto const encoded = static_cast(local_count); + auto minimum = std::uint64_t{}; + auto maximum = std::uint64_t{}; + mpi::check_or_abort(MPI_Allreduce(&encoded, &minimum, 1, MPI_UINT64_T, + MPI_MIN, communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(refinement block count minimum)"); + mpi::check_or_abort(MPI_Allreduce(&encoded, &maximum, 1, MPI_UINT64_T, + MPI_MAX, communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(refinement block count maximum)"); + if (minimum != maximum) { + mpi::abort_on_programming_error(communicator.native_handle(), + "refinement balance-management block count " + "differs across communicator"); + } + if (local_count == 0) { + mpi::abort_on_programming_error( + communicator.native_handle(), + "refinement balance management requires at least one block"); + } + if (!std::in_range(local_count)) { + mpi::abort_on_capacity_failure(communicator.native_handle(), + "refinement balance management", + "block count exceeds addressable storage"); + } + return static_cast(local_count); +} + +void require_collective_condition(bool local_condition, + mpi::communicator_view communicator, + std::string_view diagnostic) noexcept { + auto const local = local_condition ? 1 : 0; + auto all_are_valid = 0; + mpi::check_or_abort( + MPI_Allreduce(&local, &all_are_valid, 1, MPI_INT, MPI_MIN, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(refinement balance-management validation)"); + if (all_are_valid == 0) { + mpi::abort_on_programming_error(communicator.native_handle(), diagnostic); + } +} - for( long block = 0; block < (long) total_num_labels; block++) { - m_local_block_weights[block] = 0; - m_total_block_weights[block] = 0; - } - - init(); +void require_collective_capacity(bool local_condition, + mpi::communicator_view communicator, + std::string_view diagnostic) noexcept { + auto const local = local_condition ? 1 : 0; + auto all_are_representable = 0; + mpi::check_or_abort( + MPI_Allreduce(&local, &all_are_representable, 1, MPI_INT, MPI_MIN, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(refinement balance-management capacity validation)"); + if (all_are_representable == 0) { + mpi::abort_on_capacity_failure(communicator.native_handle(), + "refinement balance management", diagnostic); + } } +} // namespace -balance_management_refinement::~balance_management_refinement() { - +balance_management_refinement::balance_management_refinement( + parallel_graph_access* graph, + PartitionID total_num_labels) + : balance_management(graph, total_num_labels) { + if (graph == nullptr) { + mpi::abort_on_programming_error( + MPI_COMM_NULL, + "refinement balance management requires a graph instance"); + } + auto operation_communicator = + mpi::communicator{mpi::communicator_view{graph->getCommunicator()}}; + auto const communicator = operation_communicator.view(); + try { + auto const block_count = + validated_block_count(total_num_labels, communicator); + m_total_block_weights.assign(block_count, 0); + m_local_block_weights.assign(block_count, 0); + init(communicator); + } catch (...) { + mpi::abort_on_exception( + communicator.native_handle(), + "refinement balance-management construction failed"); + } } -// init local and total block sizes +balance_management_refinement::~balance_management_refinement() = default; + void balance_management_refinement::init() { - forall_local_nodes((*m_G), node) { - PartitionID label = m_G->getNodeLabel(node); - m_local_block_weights[label] += m_G->getNodeWeight(node); - } endfor - update(); + auto operation_communicator = + mpi::communicator{mpi::communicator_view{m_G->getCommunicator()}}; + auto const communicator = operation_communicator.view(); + try { + init(communicator); + } catch (...) { + mpi::abort_on_exception(communicator.native_handle(), + "refinement balance-management init failed"); + } +} + +void balance_management_refinement::init(mpi::communicator_view communicator) { + std::ranges::fill(m_local_block_weights, NodeWeight{}); + auto labels_are_valid = true; + auto sums_are_representable = true; + for (NodeID node = 0, node_end = m_G->number_of_local_nodes(); + node < node_end; ++node) { + auto const label = static_cast(m_G->getNodeLabel(node)); + if (!std::in_range(label) || + static_cast(label) >= m_local_block_weights.size()) { + labels_are_valid = false; + continue; + } + sums_are_representable = + checked_add(m_local_block_weights[static_cast(label)], + m_G->getNodeWeight(node)) && + sums_are_representable; + } + require_collective_capacity( + sums_are_representable, communicator, + "local block-weight sum exceeds NodeWeight capacity"); + require_collective_condition( + labels_are_valid, communicator, + "refinement balance-management label is outside [0, k)"); + update(communicator); } void balance_management_refinement::update() { - MPI_Allreduce(&m_local_block_weights[0], &m_total_block_weights[0], - m_total_num_labels, MPI_UNSIGNED_LONG_LONG, MPI_SUM, m_G->getCommunicator()); + auto operation_communicator = + mpi::communicator{mpi::communicator_view{m_G->getCommunicator()}}; + auto const communicator = operation_communicator.view(); + try { + update(communicator); + } catch (...) { + mpi::abort_on_exception(communicator.native_handle(), + "refinement balance-management update failed"); + } +} + +void balance_management_refinement::update( + mpi::communicator_view communicator) { + mpi::all_reduce_checked_sum( + std::span{m_local_block_weights}, + std::span{m_total_block_weights}, communicator, + "MPI_Allreduce(refinement block weights)", + "refinement balance management", + "global block-weight sum exceeds NodeWeight capacity"); +} + +void balance_management_refinement::setBlockSize(PartitionID block, + NodeWeight block_size) { + if (!std::in_range(block) || + static_cast(block) >= m_total_block_weights.size()) { + mpi::abort_on_programming_error( + m_G->getCommunicator(), + "refinement balance-management block is outside [0, k)"); + } + auto const index = static_cast(block); + auto const previous_total = m_total_block_weights[index]; + auto updated_local = m_local_block_weights[index]; + if (block_size >= previous_total) { + auto const increase = block_size - previous_total; + if (!checked_add(updated_local, increase)) { + mpi::abort_on_capacity_failure( + m_G->getCommunicator(), "refinement balance management", + "local block-weight update exceeds NodeWeight capacity"); + } + } else { + auto const decrease = previous_total - block_size; + if (decrease > updated_local) { + mpi::abort_on_programming_error( + m_G->getCommunicator(), + "refinement balance-management local block weight is stale"); + } + updated_local -= decrease; + } + m_local_block_weights[index] = updated_local; + m_total_block_weights[index] = block_size; +} + +auto balance_management_refinement::getBlockSize(PartitionID block) + -> NodeWeight { + if (!std::in_range(block) || + static_cast(block) >= m_total_block_weights.size()) { + mpi::abort_on_programming_error( + m_G->getCommunicator(), + "refinement balance-management block is outside [0, k)"); + } + return m_total_block_weights[static_cast(block)]; } +} // namespace parhip diff --git a/parallel/parallel_src/lib/data_structure/balance_management_refinement.h b/parallel/parallel_src/lib/data_structure/balance_management_refinement.h index 72fc0428..8572d027 100644 --- a/parallel/parallel_src/lib/data_structure/balance_management_refinement.h +++ b/parallel/parallel_src/lib/data_structure/balance_management_refinement.h @@ -9,37 +9,31 @@ #define BALANCE_MANAGEMENT_REFINEMENT_ZHYKQBYB #include "balance_management.h" - +#include "communication/mpi_handles.h" +namespace parhip { class parallel_graph_access; class balance_management_refinement : public balance_management { -public: - balance_management_refinement( parallel_graph_access * G, NodeID num_labels); - virtual ~balance_management_refinement(); - - virtual NodeWeight getBlockSize( PartitionID block ); - virtual void setBlockSize( PartitionID block, NodeWeight block_size ) ; - virtual void update_non_contained_block_balance( PartitionID from, PartitionID to, NodeWeight node_weight) {/*noop*/}; - - virtual void init(); - virtual void update(); - -private: - std::vector< NodeWeight > m_total_block_weights; - std::vector< NodeWeight > m_local_block_weights; + public: + balance_management_refinement(parallel_graph_access* graph, + PartitionID total_num_labels); + ~balance_management_refinement() override; + + [[nodiscard]] auto getBlockSize(PartitionID block) -> NodeWeight override; + void setBlockSize(PartitionID block, NodeWeight block_size) override; + void update_non_contained_block_balance(PartitionID, + PartitionID, + NodeWeight) override {} + + void init() override; + void update() override; + + private: + void init(mpi::communicator_view communicator); + void update(mpi::communicator_view communicator); + + std::vector m_total_block_weights; + std::vector m_local_block_weights; }; - - -inline -void balance_management_refinement::setBlockSize( PartitionID block, NodeWeight block_size ) { - ULONG delta = block_size - m_total_block_weights[block]; - m_local_block_weights[block] += delta; - m_total_block_weights[block] = block_size; -} - -inline -NodeWeight balance_management_refinement::getBlockSize( PartitionID block ) { - return m_total_block_weights[block]; -} - -#endif /* end of include guard: BALANCE_MANAGEMENT_REFINEMENT_ZHYKQBYB */ +} // namespace parhip +#endif // BALANCE_MANAGEMENT_REFINEMENT_ZHYKQBYB diff --git a/parallel/parallel_src/lib/data_structure/hashed_graph.h b/parallel/parallel_src/lib/data_structure/hashed_graph.h index 4ba62df0..95098895 100644 --- a/parallel/parallel_src/lib/data_structure/hashed_graph.h +++ b/parallel/parallel_src/lib/data_structure/hashed_graph.h @@ -12,7 +12,7 @@ #include "definitions.h" #include "limits.h" - +namespace parhip { struct hashed_edge { NodeID k; NodeID source; @@ -23,29 +23,26 @@ struct hashed_edge { struct compare_hashed_edge { bool operator()(const hashed_edge e_1, const hashed_edge e_2) const { bool eq = (e_1.source == e_2.source && e_1.target == e_2.target); - eq = eq || (e_1.source == e_2.target && e_1.target == e_2.source); + eq = eq || (e_1.source == e_2.target && e_1.target == e_2.source); return eq; } }; struct data_hashed_edge{ - NodeWeight weight; + EdgeWeight weight; - data_hashed_edge() { - weight = 0; - } + data_hashed_edge() { weight = 0; } }; struct hash_hashed_edge { - ULONG operator()(const hashed_edge e) const { - if(e.source < e.target) + ULONG operator()(const hashed_edge e) const { + if(e.source < e.target) return e.source*e.k + e.target; - else + else return e.target*e.k + e.source; - } + } }; typedef std::unordered_map hashed_graph; - - +} #endif /* end of include guard: HASHED_GRAPH_DG1JG7O0 */ diff --git a/parallel/parallel_src/lib/data_structure/linear_probing_hashmap.h b/parallel/parallel_src/lib/data_structure/linear_probing_hashmap.h index bd1745f3..d7cd26e2 100644 --- a/parallel/parallel_src/lib/data_structure/linear_probing_hashmap.h +++ b/parallel/parallel_src/lib/data_structure/linear_probing_hashmap.h @@ -9,7 +9,7 @@ #define LINEAR_PROBING_HASHMAP_KQ738TKS #include - +namespace parhip { const NodeID NOT_CONTAINED = std::numeric_limits::max(); struct KeyValuePair { @@ -103,6 +103,6 @@ class linear_probing_hashmap { std::vector< KeyValuePair > m_internal_map; std::stack< NodeID > m_contained_key_positions; }; - +} #endif /* end of include guard: LINEAR_PROBING_HASHMAP_KQ738TKS */ diff --git a/parallel/parallel_src/lib/data_structure/linear_probing_hashmap_ll.h b/parallel/parallel_src/lib/data_structure/linear_probing_hashmap_ll.h index a0342626..f81b96ff 100644 --- a/parallel/parallel_src/lib/data_structure/linear_probing_hashmap_ll.h +++ b/parallel/parallel_src/lib/data_structure/linear_probing_hashmap_ll.h @@ -9,6 +9,7 @@ #define LINEAR_PROBING_HASHMAP_LL_KQ738TKS #include +namespace parhip { const ULONG NOT_CONTAINED_LL = std::numeric_limits::max(); @@ -103,6 +104,6 @@ class linear_probing_hashmap_ll { std::vector< KeyValuePair > m_internal_map; std::stack< ULONG > m_contained_key_positions; }; - +} #endif /* end of include guard: LINEAR_PROBING_HASHMAP_KQ738TKS */ diff --git a/parallel/parallel_src/lib/data_structure/next_prime.h b/parallel/parallel_src/lib/data_structure/next_prime.h index 10131fce..44e81baa 100644 --- a/parallel/parallel_src/lib/data_structure/next_prime.h +++ b/parallel/parallel_src/lib/data_structure/next_prime.h @@ -7,6 +7,7 @@ #ifndef NEXT_PRIME #define NEXT_PRIME +namespace parhip { static const std::size_t small_primes[] = { @@ -135,6 +136,6 @@ next_prime(std::size_t n) } return n; } - +} #endif /* end of include guard: */ diff --git a/parallel/parallel_src/lib/data_structure/parallel_graph_access.cpp b/parallel/parallel_src/lib/data_structure/parallel_graph_access.cpp index ef13e121..f10bfecd 100644 --- a/parallel/parallel_src/lib/data_structure/parallel_graph_access.cpp +++ b/parallel/parallel_src/lib/data_structure/parallel_graph_access.cpp @@ -5,70 +5,794 @@ * Christian Schulz *****************************************************************************/ +#include "parallel_graph_access.h" #include "balance_management_coarsening.h" #include "balance_management_refinement.h" -#include "parallel_graph_access.h" +#include "communication/ghost_exchange_plan.h" +#include "communication/ghost_label_update.h" +#include "communication/mpi_async_neighbors.h" +#include "communication/mpi_failure.h" +#include "communication/mpi_neighbors.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace parhip { +struct ghost_node_communication::state final { + [[nodiscard]] static auto checked_rank_count(PEID size) -> std::size_t { + if (size < 0) { + throw std::invalid_argument{"negative ghost communicator size"}; + } + return static_cast(size); + } + + state(MPI_Comm communicator_value, PEID rank_value, PEID size_value) + : size(size_value), + rank(rank_value), + pe_packed(checked_rank_count(size_value)), + adjacent_processors(checked_rank_count(size_value)), + pending_by_rank(checked_rank_count(size_value)), + communicator(communicator_value) {} + + parallel_graph_access* graph = nullptr; + PEID size; + PEID rank; + NodeID iteration_counter = 0; + ULONG skip_limit = 0; + ULONG send_iteration = 1; + ULONG receive_iteration = 1; + ULONG desired_rounds = 0; + bool protocol_validated = false; + std::vector pe_packed; + std::vector adjacent_processors; + std::vector> pending_by_rank; + std::optional> in_flight; + ghost_exchange_plan const* exchange_plan = nullptr; + MPI_Comm communicator; +}; + +namespace { +enum class semantic_failure_action { + throw_transactionally, + abort_communicator, +}; + +struct resolved_ghost_updates final { + std::vector> local_ids_by_source; +}; + +[[nodiscard]] auto resolve_ghost_updates( + parallel_graph_access& graph, + ghost_exchange_plan const& plan, + mpi::segmented_buffer const& received, + bool require_exact_membership, + semantic_failure_action failure_action, + std::string_view context) -> resolved_ghost_updates { + auto semantic_failure = false; + auto result = resolved_ghost_updates{}; + try { + auto local_structure_is_valid = + received.segment_count() == plan.topology().sources().size(); + result.local_ids_by_source.resize(plan.topology().sources().size()); + for (std::size_t source_index = 0; + source_index < plan.topology().sources().size(); ++source_index) { + auto const source = plan.topology().sources()[source_index]; + auto const records = received.segment(source_index); + auto const expected = plan.expected_ghost_nodes(source_index); + auto& local_ids = result.local_ids_by_source[source_index]; + local_ids.reserve(records.size()); + + auto received_ids = std::vector{}; + if (require_exact_membership) { + received_ids.reserve(records.size()); + } + for (auto const& record : records) { + auto const local_id = + graph.find_ghost_local_id(record.global_id, source); + auto const belongs_to_source = + std::ranges::binary_search(expected, record.global_id); + local_structure_is_valid = local_structure_is_valid && + local_id.has_value() && belongs_to_source; + local_ids.push_back(local_id.value_or(NodeID{0})); + if (require_exact_membership) { + received_ids.push_back(record.global_id); + } + } + + if (require_exact_membership) { + std::ranges::sort(received_ids); + local_structure_is_valid = + local_structure_is_valid && records.size() == expected.size() && + std::ranges::adjacent_find(received_ids) == received_ids.end() && + std::ranges::equal(received_ids, expected); + } + } + semantic_failure = !mpi::detail::collective_predicate( + local_structure_is_valid, plan.topology().view()); + } catch (...) { + mpi::abort_on_exception(plan.topology().native_handle(), context); + } -ULONG parallel_graph_access::m_comm_rounds = 128; + if (semantic_failure) { + if (failure_action == semantic_failure_action::abort_communicator) { + mpi::abort_on_programming_error(plan.topology().native_handle(), context); + } + mpi::throw_collectively_agreed_semantic_error( + plan.topology().native_handle(), context); + } + return result; +} + +void apply_ghost_updates( + parallel_graph_access& graph, + ghost_exchange_plan const& plan, + mpi::segmented_buffer const& received, + resolved_ghost_updates const& resolved, + [[maybe_unused]] int receiver, + std::optional round, + bool update_non_contained_balance, + std::string_view context) { + try { + for (std::size_t source_index = 0; + source_index < plan.topology().sources().size(); ++source_index) { + [[maybe_unused]] auto const source = + plan.topology().sources()[source_index]; + auto const records = received.segment(source_index); + auto const& local_ids = resolved.local_ids_by_source[source_index]; + for (std::size_t record_index = 0; record_index < records.size(); + ++record_index) { + auto const& record = records[record_index]; + auto const local_id = local_ids[record_index]; + if (update_non_contained_balance) { + graph.update_non_contained_block_balance( + graph.getNodeLabel(local_id), record.label, + graph.getNodeWeight(local_id)); + } + graph.setNodeLabel(local_id, record.label); + KAHIP_MPI_TRACE(mpi::trace::ghost_update( + round.has_value() ? mpi::trace::current_hierarchy_with_round(*round) + : mpi::trace::current_hierarchy(), + record.global_id, source, receiver, record.label)); + } + } + } catch (...) { + mpi::abort_on_exception(plan.topology().native_handle(), context); + } +} + +[[nodiscard]] auto outgoing_current_labels(parallel_graph_access& graph, + ghost_exchange_plan const& plan) + -> mpi::segmented_buffer { + auto outgoing = std::vector>( + plan.topology().destinations().size()); + for (std::size_t destination_index = 0; + destination_index < plan.topology().destinations().size(); + ++destination_index) { + auto const local_nodes = plan.outgoing_local_nodes(destination_index); + auto& records = outgoing[destination_index]; + records.reserve(local_nodes.size()); + std::ranges::transform( + local_nodes, std::back_inserter(records), [&](NodeID local_node) { + return ghost_label_update{graph.getGlobalID(local_node), + graph.getNodeLabel(local_node)}; + }); + } + return mpi::segmented_buffer::from_segments(outgoing); +} +} // namespace + +ULONG parallel_graph_access::m_comm_rounds = 128; ULONG parallel_graph_access::m_comm_rounds_up = 128; -parallel_graph_access::parallel_graph_access( MPI_Comm communicator ) : m_num_local_nodes(0), - from(0), - to(0), - m_num_ghost_nodes(0), m_max_node_degree(0), m_bm(NULL) { +parallel_graph_access::parallel_graph_access() + : parallel_graph_access(MPI_COMM_WORLD) {} +parallel_graph_access::parallel_graph_access(MPI_Comm communicator) { + m_communicator = communicator; + mpi::check_or_abort(MPI_Comm_rank(m_communicator, &rank), m_communicator, + "MPI_Comm_rank(parallel graph)"); + mpi::check_or_abort(MPI_Comm_size(m_communicator, &size), m_communicator, + "MPI_Comm_size(parallel graph)"); - m_communicator = communicator; - MPI_Comm_rank( m_communicator, &rank); - MPI_Comm_size( m_communicator, &size); - - m_gnc = new ghost_node_communication(m_communicator); - m_gnc->setGraphReference(this); + try { + m_gnc = new ghost_node_communication(m_communicator, rank, size); + } catch (...) { + mpi::abort_on_exception( + m_communicator, + "parallel graph ghost communication allocation failed"); + } + m_gnc->setGraphReference(this); + m_bm = nullptr; + reset_graph_generation(); } parallel_graph_access::~parallel_graph_access() { - m_comm_rounds = std::min(m_comm_rounds, m_comm_rounds_up); - delete m_gnc; - if ( m_bm ) delete m_bm; + if (m_gnc != nullptr && !m_gnc->generation_is_idle()) { + if (mpi::runtime_is_active()) { + mpi::abort_on_programming_error( + m_communicator, + "parallel graph destroyed with an active ghost generation"); + } + mpi::abort_on_inactive_mpi_ownership( + "parallel graph destroyed with an active ghost generation"); + } + if (m_ghost_exchange_plan != nullptr && !mpi::runtime_is_active()) { + mpi::abort_on_inactive_mpi_ownership( + "parallel graph cached ghost plan destruction"); + } + m_ghost_exchange_plan.reset(); + m_comm_rounds = std::min(m_comm_rounds, m_comm_rounds_up); + delete m_gnc; + if ( m_bm ) delete m_bm; +} + +ghost_node_communication::ghost_node_communication(MPI_Comm communicator, + PEID rank, + PEID size) + : state_(std::make_unique(communicator, rank, size)) {} + +ghost_node_communication::~ghost_node_communication() = default; + +void ghost_node_communication::setGraphReference( + parallel_graph_access* graph) noexcept { + state_->graph = graph; +} + +void ghost_node_communication::init() noexcept {} + +void ghost_node_communication::add_adjacent_processor(PEID pe_id) noexcept { + if (pe_id < 0 || !std::in_range(pe_id) || + static_cast(pe_id) >= state_->adjacent_processors.size()) { + mpi::abort_on_programming_error( + state_->communicator, + "ghost communication adjacent rank is out of range"); + } + state_->adjacent_processors[static_cast(pe_id)] = true; +} + +void ghost_node_communication::set_skip_limit(ULONG skip_limit) noexcept { + state_->skip_limit = skip_limit; +} + +void ghost_node_communication::set_desired_rounds( + ULONG desired_rounds) noexcept { + state_->desired_rounds = desired_rounds; +} + +bool ghost_node_communication::is_adjacent_PE(PEID pe_id) const noexcept { + return pe_id >= 0 && std::in_range(pe_id) && + static_cast(pe_id) < state_->adjacent_processors.size() && + state_->adjacent_processors[static_cast(pe_id)]; +} + +PEID ghost_node_communication::getNumberOfAdjacentPEs() const noexcept { + auto const count = std::ranges::count(state_->adjacent_processors, true); + if (!std::in_range(count)) { + mpi::abort_on_programming_error( + state_->communicator, + "ghost communication neighbor count is not representable"); + } + return static_cast(count); +} + +bool ghost_node_communication::generation_is_idle() const noexcept { + auto const has_initial_counters = + state_->send_iteration == 1 && state_->receive_iteration == 1; + auto const has_finished_counters = + state_->send_iteration == 0 && state_->receive_iteration == 0; + + return !state_->in_flight.has_value() && state_->iteration_counter == 0 && + std::ranges::none_of(state_->pe_packed, std::identity{}) && + (has_initial_counters || has_finished_counters); +} + +void ghost_node_communication::reset_generation() noexcept { + std::ranges::fill(state_->pe_packed, false); + std::ranges::fill(state_->adjacent_processors, false); + for (auto& buffer : state_->pending_by_rank) { + buffer.clear(); + } + state_->in_flight.reset(); + state_->exchange_plan = nullptr; + state_->iteration_counter = 0; + state_->skip_limit = 0; + state_->send_iteration = 1; + state_->receive_iteration = 1; + state_->desired_rounds = 0; + state_->protocol_validated = false; +} + +void ghost_node_communication::addLabel(NodeID node, NodeID label) { + if (state_->graph == nullptr) { + mpi::abort_on_programming_error( + state_->communicator, + "ghost label buffering requires an attached graph"); + } + + try { + for (auto edge = state_->graph->get_first_edge(node), + end = state_->graph->get_first_invalid_edge(node); + edge < end; ++edge) { + auto const target = state_->graph->getEdgeTarget(edge); + if (state_->graph->is_local_node(target)) { + continue; + } + auto const destination = state_->graph->getTargetPE(target); + if (destination < 0 || !std::in_range(destination) || + static_cast(destination) >= + state_->pending_by_rank.size()) { + mpi::abort_on_programming_error( + state_->communicator, + "ghost label destination rank is out of range"); + } + auto const destination_index = static_cast(destination); + if (!state_->pe_packed[destination_index]) { + state_->pending_by_rank[destination_index].push_back( + {state_->graph->getGlobalID(node), label}); + state_->pe_packed[destination_index] = true; + } + } + for (auto edge = state_->graph->get_first_edge(node), + end = state_->graph->get_first_invalid_edge(node); + edge < end; ++edge) { + auto const target = state_->graph->getEdgeTarget(edge); + if (!state_->graph->is_local_node(target)) { + auto const destination = state_->graph->getTargetPE(target); + state_->pe_packed[static_cast(destination)] = false; + } + } + } catch (...) { + mpi::abort_on_exception(state_->communicator, + "ghost label buffering failed"); + } +} + +void ghost_node_communication::validate_incremental_protocol( + ghost_exchange_plan const& plan) { + if (state_->protocol_validated) { + return; + } + + auto const local_protocol = std::array{ + state_->desired_rounds, + state_->send_iteration, + state_->receive_iteration, + }; + auto minimum_protocol = local_protocol; + auto maximum_protocol = local_protocol; + auto const communicator = plan.topology().native_handle(); + mpi::check_or_abort( + MPI_Allreduce(local_protocol.data(), minimum_protocol.data(), + static_cast(local_protocol.size()), + mpi::get_mpi_datatype(), MPI_MIN, communicator), + communicator, "MPI_Allreduce(ghost label incremental protocol minimum)"); + mpi::check_or_abort( + MPI_Allreduce(local_protocol.data(), maximum_protocol.data(), + static_cast(local_protocol.size()), + mpi::get_mpi_datatype(), MPI_MAX, communicator), + communicator, "MPI_Allreduce(ghost label incremental protocol maximum)"); + if (minimum_protocol != maximum_protocol) { + mpi::abort_on_programming_error( + communicator, "ghost label incremental protocol diverged across ranks"); + } + state_->protocol_validated = true; +} + +void ghost_node_communication::post_pending_round() { + if (state_->graph == nullptr || state_->in_flight.has_value()) { + mpi::abort_on_programming_error( + state_->communicator, + state_->graph == nullptr + ? "ghost label post requires an attached graph" + : "ghost label post requires no active exchange"); + } + + auto const& plan = state_->exchange_plan == nullptr + ? state_->graph->ghost_plan() + : *state_->exchange_plan; + state_->exchange_plan = std::addressof(plan); + validate_incremental_protocol(plan); + try { + auto outgoing = std::vector>{}; + outgoing.reserve(plan.topology().destinations().size()); + auto destination_is_present = + std::vector(state_->pending_by_rank.size(), false); + for (auto const destination : plan.topology().destinations()) { + if (destination < 0 || !std::in_range(destination) || + static_cast(destination) >= + state_->pending_by_rank.size()) { + mpi::abort_on_programming_error( + plan.topology().native_handle(), + "ghost label topology destination is out of range"); + } + auto const destination_index = static_cast(destination); + destination_is_present[destination_index] = true; + outgoing.emplace_back(state_->pending_by_rank[destination_index]); + } + for (std::size_t rank_index = 0; + rank_index < state_->pending_by_rank.size(); ++rank_index) { + if (!state_->pending_by_rank[rank_index].empty() && + !destination_is_present[rank_index]) { + mpi::abort_on_programming_error( + plan.topology().native_handle(), + "ghost label buffer targets a rank outside the ghost topology"); + } + } + + auto sends = + mpi::segmented_buffer::from_segments(outgoing); + state_->in_flight.emplace( + mpi::start_neighbor_all_to_all_v(std::move(sends), plan.topology())); + for (auto const destination : plan.topology().destinations()) { + state_->pending_by_rank[static_cast(destination)].clear(); + } + } catch (...) { + mpi::abort_on_exception(plan.topology().native_handle(), + "ghost label exchange post failed"); + } +} + +void ghost_node_communication::receive_messages_of_neighbors() { + if (state_->graph == nullptr || !state_->in_flight.has_value()) { + mpi::abort_on_programming_error( + state_->communicator, + state_->graph == nullptr + ? "ghost label completion requires an attached graph" + : "ghost label completion requires an active exchange"); + } + if (state_->receive_iteration == std::numeric_limits::max()) { + mpi::abort_on_programming_error( + state_->communicator, "ghost label receive-round counter overflow"); + } + auto const next_receive_iteration = state_->receive_iteration + ULONG{1}; + if (!std::in_range(next_receive_iteration)) { + mpi::abort_on_programming_error( + state_->communicator, "ghost label trace round is not representable"); + } + + auto received = std::move(*state_->in_flight).wait(); + state_->in_flight.reset(); + if (state_->exchange_plan == nullptr) { + mpi::abort_on_programming_error( + state_->communicator, "ghost label completion has no cached topology"); + } + auto const& plan = *state_->exchange_plan; + auto resolved = resolve_ghost_updates( + *state_->graph, plan, received, false, + semantic_failure_action::abort_communicator, + "incremental ghost label receive validation failed after payload " + "completion"); + apply_ghost_updates(*state_->graph, plan, received, resolved, state_->rank, + static_cast(next_receive_iteration), true, + "incremental ghost label application failed"); + state_->receive_iteration = next_receive_iteration; +} + +void ghost_node_communication::update_ghost_node_data( + bool check_iteration_counter) { + if (check_iteration_counter) { + if (state_->iteration_counter == std::numeric_limits::max()) { + mpi::abort_on_programming_error(state_->communicator, + "ghost label skip counter overflow"); + } + ++state_->iteration_counter; + if (state_->iteration_counter <= state_->skip_limit || state_->size == 1) { + return; + } + } + + state_->iteration_counter = 0; + if (state_->send_iteration == std::numeric_limits::max()) { + mpi::abort_on_programming_error(state_->communicator, + "ghost label send-round counter overflow"); + } + ++state_->send_iteration; + + if (!state_->in_flight.has_value()) { + post_pending_round(); + return; + } + + state_->graph->update_block_weights(); + receive_messages_of_neighbors(); + post_pending_round(); +} + +void ghost_node_communication::update_ghost_node_data_finish() { + while (state_->send_iteration < state_->desired_rounds) { + update_ghost_node_data(false); + } + while (state_->receive_iteration < state_->desired_rounds) { + receive_messages_of_neighbors(); + } + if (state_->in_flight.has_value()) { + mpi::abort_on_programming_error( + state_->communicator, + "ghost label finish reached the final round with an active exchange"); + } + + update_ghost_node_data(false); + state_->graph->update_block_weights(); + receive_messages_of_neighbors(); + + state_->send_iteration = 0; + state_->receive_iteration = 0; + state_->protocol_validated = false; + state_->iteration_counter = 0; + for (auto& buffer : state_->pending_by_rank) { + buffer.clear(); + } +} + +void ghost_node_communication::update_ghost_node_data_global() { + if (state_->graph == nullptr) { + mpi::abort_on_programming_error( + state_->communicator, + "global ghost label exchange requires an attached graph"); + } + if (!generation_is_idle()) { + mpi::abort_on_programming_error( + state_->communicator, + "global ghost label exchange requires no active incremental " + "exchange"); + } + auto const& plan = state_->exchange_plan == nullptr + ? state_->graph->ghost_plan() + : *state_->exchange_plan; + state_->exchange_plan = std::addressof(plan); + auto received = mpi::segmented_buffer{}; + try { + received = mpi::neighbor_all_to_all_v( + outgoing_current_labels(*state_->graph, plan), plan.topology()); + } catch (...) { + mpi::abort_on_exception(plan.topology().native_handle(), + "global ghost label exchange failed"); + } + + auto resolved = + resolve_ghost_updates(*state_->graph, plan, received, true, + semantic_failure_action::throw_transactionally, + "global ghost label receive validation failed"); + apply_ghost_updates(*state_->graph, plan, received, resolved, state_->rank, + std::nullopt, false, + "global ghost label application failed"); +} + +void parallel_graph_access::reset_graph_generation() { + if (!m_gnc->generation_is_idle()) { + mpi::abort_on_programming_error( + m_communicator, + "parallel graph reset requires idle ghost communication"); + } + if (m_ghost_exchange_plan != nullptr) { + if (!mpi::runtime_is_active()) { + mpi::abort_on_inactive_mpi_ownership( + "parallel graph cached ghost plan reset"); + } + m_ghost_exchange_plan.reset(); + } + if (m_bm != nullptr) { + delete m_bm; + m_bm = nullptr; + } + m_gnc->reset_generation(); + + m_nodes.clear(); + m_nodes_data.clear(); + m_edges.clear(); + m_add_non_local_node_data.clear(); + m_nodes_to_cnode.clear(); + m_range_array.clear(); + m_edge_range_array.clear(); + m_global_to_local_id.clear(); + + m_ghost_adddata_array_offset = 0; + m_divisor = 0; + m_num_local_nodes = 0; + from = 0; + to = 0; + m_building_graph = false; + m_graph_construction_complete = false; + m_last_source = std::numeric_limits::max(); + m_num_ghost_nodes = 0; + node = 0; + e = 0; + m_num_nodes = 0; + m_global_n = 0; + m_global_m = 0; + m_max_node_degree = 0; + m_cur_degree = 0; +} + +void parallel_graph_access::start_construction(NodeID n, + EdgeID m, + NodeID global_n, + NodeID global_m, + bool update_comm_rounds) { + reset_graph_generation(); + if (n == std::numeric_limits::max()) { + mpi::abort_on_programming_error( + m_communicator, + "parallel graph node count cannot represent its sentinel"); + } + auto const stored_node_count = n + 1; + if (!std::in_range(stored_node_count) || + !std::in_range(m)) { + mpi::abort_on_programming_error( + m_communicator, + "parallel graph storage size is not representable"); + } + + try { + m_building_graph = true; + m_graph_construction_complete = false; + m_num_nodes = stored_node_count; + m_num_local_nodes = n; + m_global_n = global_n; + m_global_m = global_m; + m_ghost_adddata_array_offset = stored_node_count; + + m_nodes.resize(static_cast(stored_node_count)); + m_nodes_data.resize(static_cast(stored_node_count)); + m_edges.resize(static_cast(m)); + m_nodes[0].firstEdge = 0; + m_divisor = static_cast(std::ceil(global_n / (double)size)); + } catch (...) { + mpi::abort_on_exception( + m_communicator, + "parallel graph storage allocation failed"); + } + + if (update_comm_rounds) { + m_comm_rounds = std::max(m_comm_rounds, 8ULL); + m_gnc->set_desired_rounds(m_comm_rounds); + m_gnc->set_skip_limit( + static_cast(std::ceil(n / (double)m_comm_rounds))); + } +} + +void parallel_graph_access::reinit() { reset_graph_generation(); } + +auto parallel_graph_access::node_to_cnode_storage_size() const noexcept + -> std::size_t { + return m_nodes.size(); +} + +void parallel_graph_access::replace_node_to_cnode( + std::vector&& replacement) noexcept { + if (replacement.size() != m_nodes.size()) { + mpi::abort_on_programming_error( + m_communicator, "parallel graph CNode replacement size mismatch"); + } + m_nodes_to_cnode.swap(replacement); +} + +auto parallel_graph_access::find_local_id(NodeID global_id) const noexcept + -> std::optional { + if (global_id < from) { + return std::nullopt; + } + auto const offset = global_id - from; + if (offset >= m_num_local_nodes) { + return std::nullopt; + } + return offset; +} + +auto parallel_graph_access::find_ghost_local_id( + NodeID global_id, PEID expected_owner) const noexcept + -> std::optional { + auto const mapping = m_global_to_local_id.find(global_id); + if (mapping == m_global_to_local_id.end()) { + return std::nullopt; + } + auto const local_id = mapping->second; + if (local_id < m_ghost_adddata_array_offset || + !std::in_range(local_id) || + static_cast(local_id) >= m_nodes.size() || + static_cast(local_id) >= m_nodes_data.size()) { + return std::nullopt; + } + auto const ghost_index = local_id - m_ghost_adddata_array_offset; + if (!std::in_range(ghost_index) || + static_cast(ghost_index) >= + m_add_non_local_node_data.size()) { + return std::nullopt; + } + auto const& metadata = + m_add_non_local_node_data[static_cast(ghost_index)]; + if (metadata.globalID != global_id || metadata.peID != expected_owner) { + return std::nullopt; + } + return local_id; +} + +auto parallel_graph_access::ghost_plan() -> ghost_exchange_plan const& { + auto const view = mpi::communicator_view{m_communicator}; + if (!mpi::detail::collective_predicate( + m_graph_construction_complete && !m_building_graph, view)) { + mpi::throw_collectively_agreed_semantic_error( + m_communicator, + "ghost exchange plan requires completed graph construction"); + } + + auto const local_cache = m_ghost_exchange_plan == nullptr ? 0 : 1; + auto minimum_cache = 0; + auto maximum_cache = 0; + mpi::check_or_abort(MPI_Allreduce(&local_cache, &minimum_cache, 1, MPI_INT, + MPI_MIN, m_communicator), + m_communicator, + "MPI_Allreduce(ghost plan cache minimum)"); + mpi::check_or_abort(MPI_Allreduce(&local_cache, &maximum_cache, 1, MPI_INT, + MPI_MAX, m_communicator), + m_communicator, + "MPI_Allreduce(ghost plan cache maximum)"); + if (minimum_cache != maximum_cache) { + mpi::abort_on_programming_error( + m_communicator, "ghost exchange plan cache state diverged across ranks"); + } + if (minimum_cache != 0) { + return *m_ghost_exchange_plan; + } + + auto candidate = make_ghost_exchange_plan(*this); + if (candidate == nullptr) { + mpi::abort_on_programming_error( + m_communicator, "ghost exchange plan factory returned no plan"); + } + m_ghost_exchange_plan = std::move(candidate); + return *m_ghost_exchange_plan; } void parallel_graph_access::init_balance_management( PPartitionConfig & config ) { - if( m_bm != NULL ) { - delete m_bm; - } + if( m_bm != NULL ) { + delete m_bm; + } - if( config.total_num_labels != config.k ) { - m_bm = new balance_management_coarsening( this, config.total_num_labels ); - } else { - m_bm = new balance_management_refinement( this, config.total_num_labels ); - } + if( config.total_num_labels != config.k ) { + m_bm = new balance_management_coarsening( this, config.total_num_labels ); + } else { + m_bm = new balance_management_refinement( this, config.total_num_labels ); + } } void parallel_graph_access::update_non_contained_block_balance( PartitionID from, PartitionID to, NodeWeight node_weight) { - m_bm->update_non_contained_block_balance( from, to, node_weight); + m_bm->update_non_contained_block_balance( from, to, node_weight); } void parallel_graph_access::update_block_weights() { - m_bm->update(); + m_bm->update(); } void parallel_graph_access::update_ghost_node_data( bool check_iteration_counter ) { - m_gnc->update_ghost_node_data( check_iteration_counter ); + m_gnc->update_ghost_node_data( check_iteration_counter ); } void parallel_graph_access::update_ghost_node_data_global() { - m_gnc->update_ghost_node_data_global(); + m_gnc->update_ghost_node_data_global(); } void parallel_graph_access::update_ghost_node_data_finish() { - m_gnc->update_ghost_node_data_finish(); + m_gnc->update_ghost_node_data_finish(); } void parallel_graph_access::set_comm_rounds(ULONG comm_rounds) { - m_comm_rounds = comm_rounds; - set_comm_rounds_up(comm_rounds); + m_comm_rounds = comm_rounds; + set_comm_rounds_up(comm_rounds); } void parallel_graph_access::set_comm_rounds_up(ULONG comm_rounds) { - m_comm_rounds_up = comm_rounds; + m_comm_rounds_up = comm_rounds; +} } - diff --git a/parallel/parallel_src/lib/data_structure/parallel_graph_access.h b/parallel/parallel_src/lib/data_structure/parallel_graph_access.h index 7e73ffa9..2c376f0b 100644 --- a/parallel/parallel_src/lib/data_structure/parallel_graph_access.h +++ b/parallel/parallel_src/lib/data_structure/parallel_graph_access.h @@ -8,8 +8,10 @@ #ifndef PARALLEL_GRAPH_ACCESS_X6O9MRS8 #define PARALLEL_GRAPH_ACCESS_X6O9MRS8 - +#include +#include #include +#include #include #include #include @@ -17,40 +19,43 @@ #include #include "data_structure/balance_management.h" +#include "communication/mpi_trace.h" #include "definitions.h" #include "partition_config.h" +#include "range_owner.h" #include "tools/timer.h" - +namespace parhip { struct Node { - EdgeID firstEdge; + EdgeID firstEdge; }; + struct NodeData { - NodeID label; - PartitionID block; // a given partition of the graph (for v-cycles) - NodeWeight weight; // save a little bit of memory - bool is_interface_node; // save a little bit of memory + NodeID label; + PartitionID block; // a given partition of the graph (for v-cycles) + NodeWeight weight; // save a little bit of memory + bool is_interface_node; // save a little bit of memory }; //struct NodeData { - //NodeID label; - //PartitionID block:15; // a given partition of the graph (for v-cycles) - //NodeWeight weight:47; // save a little bit of memory - //bool is_interface_node:1; // save a little bit of memory +//NodeID label; +//PartitionID block:15; // a given partition of the graph (for v-cycles) +//NodeWeight weight:47; // save a little bit of memory +//bool is_interface_node:1; // save a little bit of memory //}; //struct AdditionalNonLocalNodeData { - //PEID peID:15; // save a little bit of memory - //NodeID globalID:48; +//PEID peID:15; // save a little bit of memory +//NodeID globalID:48; //}; struct AdditionalNonLocalNodeData { - PEID peID; // save a little bit of memory - NodeID globalID; + PEID peID; // save a little bit of memory + NodeID globalID; }; struct Edge { - NodeID local_target; - EdgeWeight weight; + NodeID local_target; + EdgeWeight weight; }; //makros - graph access @@ -60,125 +65,50 @@ struct Edge { #define forall_out_edges(G,e,n) { for(EdgeID e = G.get_first_edge(n), end = G.get_first_invalid_edge(n); e < end; ++e) { #define endfor }} + class parallel_graph_access; +class ghost_exchange_plan; -//handle communication of data associated with ghost nodes +// handle communication of data associated with ghost nodes class ghost_node_communication { -public: - ghost_node_communication(MPI_Comm communicator) : m_iteration_counter(0), m_first_send(true) { - m_communicator = communicator; - - MPI_Comm_rank( m_communicator, &m_rank); - MPI_Comm_size( m_communicator, &m_size); - - m_PE_packed.resize(m_size); - m_adjacent_processors.resize(m_size); - for( PEID peID = 0; peID < (PEID) m_PE_packed.size(); peID++) { - m_PE_packed[ peID ] = false; - m_adjacent_processors[ peID ] = false; - } - - m_send_buffers_A.resize(m_size); - m_send_buffers_B.resize(m_size); - m_send_buffers_ptr = & m_send_buffers_A; - m_send_iteration = 1; - m_recv_iteration = 1; - - m_send_tag = 100*m_size; - m_recv_tag = 100*m_size; - - }; - - virtual ~ghost_node_communication() {}; - - inline - void setGraphReference( parallel_graph_access * G ) { - m_G = G; - }; - - inline - void init( ) { - m_num_adjacent = 0; - for( PEID peID = 0; peID < (PEID)m_adjacent_processors.size(); peID++) { - if( m_adjacent_processors[peID] ) { - m_num_adjacent++; - } - } - }; - - - inline - void add_adjacent_processor( PEID peID) { - m_adjacent_processors[peID] = true; - }; - - inline - void set_skip_limit( ULONG skip_limit ) { - m_skip_limit = skip_limit; - } - - inline - void set_desired_rounds( ULONG desired_rounds) { - m_desired_rounds = desired_rounds; - } - - inline - void update_ghost_node_data( bool check_iteration_counter ); - - inline - void update_ghost_node_data_finish(); - - inline - void update_ghost_node_data_global(); - - inline - void addLabel(NodeID node, NodeID label); - - inline - bool is_adjacent_PE(PEID peID) { - return m_adjacent_processors[peID]; - } + public: + ghost_node_communication(MPI_Comm communicator, PEID rank, PEID size); - inline - PEID getNumberOfAdjacentPEs() { - PEID counter = 0; - for( PEID peID = 0; peID < (PEID)m_adjacent_processors.size(); peID++) { - if( m_adjacent_processors[peID] ) counter++; - } - return counter; - } - -private: + ~ghost_node_communication(); - inline - void receive_messages_of_neighbors(); + ghost_node_communication(ghost_node_communication const&) = delete; + auto operator=(ghost_node_communication const&) + -> ghost_node_communication& = delete; + ghost_node_communication(ghost_node_communication&&) = delete; + auto operator=(ghost_node_communication&&) + -> ghost_node_communication& = delete; - parallel_graph_access * m_G; - PEID m_size; - PEID m_rank; - NodeID m_iteration_counter; // this counter is used to manage the communication rounds - ULONG m_skip_limit; - bool m_first_send; + void setGraphReference(parallel_graph_access* graph) noexcept; + void init() noexcept; + void add_adjacent_processor(PEID pe_id) noexcept; + void set_skip_limit(ULONG skip_limit) noexcept; + void set_desired_rounds(ULONG desired_rounds) noexcept; - ULONG m_send_iteration; - ULONG m_recv_iteration; + void update_ghost_node_data(bool check_iteration_counter); + void update_ghost_node_data_finish(); + void update_ghost_node_data_global(); + void addLabel(NodeID node, NodeID label); - ULONG m_send_tag; - ULONG m_recv_tag; + [[nodiscard]] bool is_adjacent_PE(PEID pe_id) const noexcept; + [[nodiscard]] PEID getNumberOfAdjacentPEs() const noexcept; - ULONG m_desired_rounds; + private: + friend class parallel_graph_access; - // store the number of adjacent processors ( a block is a neighbor iff there is an edge between the subgraphs ) - PEID m_num_adjacent; + [[nodiscard]] bool generation_is_idle() const noexcept; + void reset_generation() noexcept; - std::vector< bool > m_PE_packed; - std::vector< std::vector< NodeID > > m_send_buffers_A; // buffers to send messages - std::vector< std::vector< NodeID > > m_send_buffers_B; // buffers to send messages - std::vector< std::vector< NodeID > >* m_send_buffers_ptr; // pointer to current buffers to send messages - std::vector< bool > m_adjacent_processors; // buffers to send messages - std::vector< MPI_Request* > m_isend_requests; + void receive_messages_of_neighbors(); + void post_pending_round(); + void validate_incremental_protocol(ghost_exchange_plan const& plan); - MPI_Comm m_communicator; + struct state; + std::unique_ptr state_; }; @@ -186,61 +116,39 @@ class parallel_graph_access { public: friend class ghost_node_communication; + friend auto make_ghost_exchange_plan( + parallel_graph_access const& graph) + -> std::unique_ptr; - parallel_graph_access( ) : m_num_local_nodes(0), - from(0), - to(0), - m_num_ghost_nodes(0), m_max_node_degree(0), m_bm(NULL) { - m_communicator = MPI_COMM_WORLD; - MPI_Comm_rank( m_communicator, &rank); - MPI_Comm_size( m_communicator, &size); - - m_gnc = new ghost_node_communication(m_communicator); - m_gnc->setGraphReference(this); - }; + parallel_graph_access(); parallel_graph_access( MPI_Comm communicator ); virtual ~parallel_graph_access(); + parallel_graph_access(parallel_graph_access const&) = delete; + auto operator=(parallel_graph_access const&) + -> parallel_graph_access& = delete; + parallel_graph_access(parallel_graph_access&&) = delete; + auto operator=(parallel_graph_access&&) + -> parallel_graph_access& = delete; + /* ============================================================= */ /* build methods */ /* ============================================================= */ - void start_construction(NodeID n, EdgeID m, NodeID global_n, NodeID global_m, bool update_comm_rounds = true) { - m_building_graph = true; - node = 0; - e = 0; - m_last_source = -1; - m_num_nodes = n+1; - m_num_local_nodes = n; - m_global_n = global_n; - m_global_m = global_m; - m_ghost_adddata_array_offset = n+1; - m_bm = NULL; - m_cur_degree = 0; - - //resizes property arrays - m_nodes.resize(n+1); - m_nodes_data.resize(n+1); - m_edges.resize(m); - - m_nodes[node].firstEdge = e; - m_divisor = ceil(global_n / (double)size); - // every PE has to make same amount communication iterations - // we use ceil an check afterwards wether everyone has done the right - // amount of communication rounds - if( update_comm_rounds ) { - m_comm_rounds = std::max(m_comm_rounds, 8ULL); - m_gnc->set_desired_rounds(m_comm_rounds); - m_gnc->set_skip_limit(ceil(n/(double)m_comm_rounds)); - } - }; + void start_construction(NodeID n, EdgeID m, NodeID global_n, + NodeID global_m, + bool update_comm_rounds = true); void set_range(NodeID l, NodeID r) { from = l; to = r; }; + [[nodiscard]] bool contains_global_node(NodeID global_id) const noexcept { + return global_id >= from && global_id - from < m_num_local_nodes; + } + NodeID get_from_range() { return from; }; @@ -266,17 +174,11 @@ class parallel_graph_access { }; PEID get_PEID_from_range_array(NodeID node) { - // TODO optimize with binary search - for( PEID peID = 1; peID < (PEID)m_range_array.size(); peID++) { - if( node < m_range_array[peID] ) { - return (peID-1); - } - } - return -1; + return kahip::range_owner::from_boundaries(m_range_array, node); }; NodeID new_node() { - m_cur_degree = 0; + m_cur_degree = 0; ASSERT_TRUE(m_building_graph); return node++; }; @@ -286,26 +188,26 @@ class parallel_graph_access { ASSERT_TRUE(e < m_edges.size()); // build ghost nodes on the fly - if( from <= target && target <= to) { - m_edges[e].local_target = target - from; + if( contains_global_node(target) ) { + m_edges[e].local_target = target - from; } else { m_nodes_data[source].is_interface_node = true; // check wether this is already a ghost node if(m_global_to_local_id.find(target) != m_global_to_local_id.end()) { // this node is already a ghost node - m_edges[e].local_target = m_global_to_local_id[target]; + m_edges[e].local_target = m_global_to_local_id[target]; } else { // we need to create a new ghost node m_global_to_local_id[target] = m_num_nodes++; - m_edges[e].local_target = m_global_to_local_id[target]; + m_edges[e].local_target = m_global_to_local_id[target]; //create the ghost node in the array Node dummy; dummy.firstEdge = 0; m_nodes.push_back(dummy); - NodeData dummy_data; + NodeData dummy_data; dummy_data.label = target; dummy_data.block = 0; dummy_data.is_interface_node = false; @@ -314,8 +216,8 @@ class parallel_graph_access { // add addtional data AdditionalNonLocalNodeData add_data; - //has to be changed once we implement better load balancing - //add_data.peID = target / m_divisor; + //has to be changed once we implement better load balancing + //add_data.peID = target / m_divisor; add_data.peID = get_PEID_from_range_array(target); add_data.globalID = target; @@ -337,11 +239,11 @@ class parallel_graph_access { } } m_last_source = source; - m_cur_degree++; + m_cur_degree++; - if( m_cur_degree > m_max_node_degree ) { - m_max_node_degree = m_cur_degree; - } + if( m_cur_degree > m_max_node_degree ) { + m_max_node_degree = m_cur_degree; + } return e_bar; }; @@ -349,6 +251,7 @@ class parallel_graph_access { void finish_construction() { m_edges.resize(e); m_building_graph = false; + m_graph_construction_complete = true; //fill isolated sources at the end if ((NodeID)(m_last_source) != node-1) { @@ -361,9 +264,9 @@ class parallel_graph_access { m_gnc->init(); }; - NodeID get_max_degree() { - return m_max_node_degree; - } + NodeID get_max_degree() { + return m_max_node_degree; + } /* ============================================================= */ /* methods handeling balance */ /* ============================================================= */ @@ -380,7 +283,12 @@ class parallel_graph_access { /* parallel graph access methods */ /* ============================================================= */ NodeID number_of_local_nodes() {return m_num_local_nodes;}; - NodeID number_of_ghost_nodes() {return m_nodes.size() - m_num_local_nodes - 1;}; + NodeID number_of_ghost_nodes() { + return m_nodes.empty() + ? NodeID{0} + : static_cast(m_nodes.size()) - + m_num_local_nodes - 1; + }; NodeID number_of_global_nodes() {return m_global_n;}; EdgeID number_of_local_edges() {return m_edges.size();}; EdgeID number_of_global_edges() {return m_global_m;}; @@ -390,6 +298,10 @@ class parallel_graph_access { m_nodes_to_cnode.resize( m_nodes.size() ); } + [[nodiscard]] auto node_to_cnode_storage_size() const noexcept + -> std::size_t; + void replace_node_to_cnode(std::vector&& replacement) noexcept; + void setCNode( NodeID node, NodeID cnode) { m_nodes_to_cnode[ node ] = cnode; } @@ -401,15 +313,15 @@ class parallel_graph_access { EdgeID get_first_edge(NodeID node); EdgeID get_first_invalid_edge(NodeID node); - NodeID getNodeLabel(NodeID node); - void setNodeLabel(NodeID node, NodeID label); + NodeID getNodeLabel(NodeID node); + void setNodeLabel(NodeID node, NodeID label); - NodeID getSecondPartitionIndex(NodeID node); - void setSecondPartitionIndex(NodeID node, NodeID label); + NodeID getSecondPartitionIndex(NodeID node); + void setSecondPartitionIndex(NodeID node, NodeID label); - NodeWeight getNodeWeight(NodeID node); - void setNodeWeight(NodeID node, NodeWeight weight); + NodeWeight getNodeWeight(NodeID node); + void setNodeWeight(NodeID node, NodeWeight weight); EdgeID getNodeDegree(NodeID node); EdgeID getNodeNumGhostNodes(NodeID node); @@ -430,8 +342,8 @@ class parallel_graph_access { return m_communicator; } - EdgeWeight getEdgeWeight(EdgeID e); - void setEdgeWeight(EdgeID e, EdgeWeight weight); + EdgeWeight getEdgeWeight(EdgeID e); + void setEdgeWeight(EdgeID e, EdgeWeight weight); NodeID getEdgeTarget(EdgeID e); @@ -439,16 +351,24 @@ class parallel_graph_access { //these methods are usally called to communicate data PEID getTargetPE(NodeID node); - //input is a global id + //input is a global id //output is the local id NodeID getLocalID(NodeID node) { - if( from <= node && node <= to ) { + if( contains_global_node(node) ) { return node - from; } else { return m_global_to_local_id[node]; } }; + [[nodiscard]] auto find_local_id(NodeID global_id) const noexcept + -> std::optional; + [[nodiscard]] auto find_ghost_local_id(NodeID global_id, + PEID expected_owner) + const noexcept -> std::optional; + + [[nodiscard]] auto ghost_plan() -> ghost_exchange_plan const&; + //methods for local nodes only NodeID getGlobalID(NodeID node); @@ -470,8 +390,8 @@ class parallel_graph_access { void update_ghost_node_data_finish(); void update_ghost_node_data_global(); - static void set_comm_rounds(ULONG comm_rounds); - static void set_comm_rounds_up(ULONG comm_rounds); + static void set_comm_rounds(ULONG comm_rounds); + static void set_comm_rounds_up(ULONG comm_rounds); /* ============================================================= */ /* info */ @@ -504,11 +424,13 @@ class parallel_graph_access { /* parallel graph data structure */ /* ============================================================= */ private: + void reset_graph_generation(); + // the graph representation itself - // local and ghost nodes in one array, + // local and ghost nodes in one array, // local nodes are stored in the beginning // ghost nodes in the end of the array - std::vector m_nodes; + std::vector m_nodes; std::vector m_nodes_data; std::vector m_edges; @@ -516,7 +438,7 @@ class parallel_graph_access { std::vector m_add_non_local_node_data; // NodeID to CNode for ghost nodes and local nodes - std::vector m_nodes_to_cnode; + std::vector m_nodes_to_cnode; // stores the ranges for which a processor is responsible for // m_range_array[i]= starting position of PE i @@ -525,32 +447,34 @@ class parallel_graph_access { std::unordered_map m_global_to_local_id; - NodeID m_ghost_adddata_array_offset; // node id of ghost node - offset to get the position in add data + NodeID m_ghost_adddata_array_offset; // node id of ghost node - offset to get the position in add data NodeID m_divisor; // needed to compute the target id of a ghost node NodeID m_num_local_nodes; // store the number of local / non-ghost nodes NodeID from; // each process stores nodes [from. to] - NodeID to; - + NodeID to; + // construction properties bool m_building_graph; + bool m_graph_construction_complete; NodeID m_last_source; NodeID m_num_ghost_nodes; NodeID node; //current node that is constructed EdgeID e; //current edge that is constructed - NodeID m_num_nodes; + NodeID m_num_nodes; NodeID m_global_n; // global number of nodes NodeID m_global_m; // global number of edges static ULONG m_comm_rounds; // global number of edges static ULONG m_comm_rounds_up; // global number of edges - NodeID m_max_node_degree; - NodeID m_cur_degree; + NodeID m_max_node_degree; + NodeID m_cur_degree; PEID size; PEID rank; ghost_node_communication* m_gnc; + std::unique_ptr m_ghost_exchange_plan; balance_management* m_bm; MPI_Comm m_communicator; @@ -648,7 +572,7 @@ inline bool parallel_graph_access::is_interface_node(NodeID node) { } inline bool parallel_graph_access::is_local_node_from_global_id(NodeID node) { - return from <= node && node <= to; + return contains_global_node(node); } inline bool parallel_graph_access::is_local_node(NodeID node) { @@ -787,256 +711,38 @@ inline int parallel_graph_access::build_from_metis_weighted(int n, int* xadj, in } -//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -//%%%%%%%%%%%%%%%%% Handle Communication of Ghost Node Data %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -inline -void ghost_node_communication::addLabel(NodeID node, NodeID label) { - forall_out_edges((*m_G), e, node) { - NodeID target = m_G->getEdgeTarget(e); - if( !m_G->is_local_node(target) ) { - PEID peID = m_G->getTargetPE(target); - if( !m_PE_packed[peID] ) { // make sure a node is sent at most once - (*m_send_buffers_ptr)[peID].push_back(m_G->getGlobalID(node)); - (*m_send_buffers_ptr)[peID].push_back(label); - m_PE_packed[peID] = true; - } - } - } endfor - forall_out_edges((*m_G), e, node) { - NodeID target = m_G->getEdgeTarget(e); - if( !m_G->is_local_node(target) ) { - m_PE_packed[m_G->getTargetPE(target)] = false; - } - } endfor -} - -// we want to interleave computation and communication -// check_iteration_counter default is true -inline void ghost_node_communication::update_ghost_node_data( bool check_iteration_counter ) { - if( check_iteration_counter ) { - if( ++m_iteration_counter <= m_skip_limit || m_size == 1 ) return; - } - m_iteration_counter = 0; - m_send_iteration++; - m_send_tag++; - - //send all neighbors their packages using Isends - //a neighbor that does not receive something gets a specific token - if( m_first_send ) { - for( PEID peID = 0; peID < m_size; peID++) { - if( m_adjacent_processors[peID] ) { - //now we have to send a message - if( (*m_send_buffers_ptr)[peID].size() == 0 ){ - // length 1 encode no message - (*m_send_buffers_ptr)[peID].push_back(0); - } - - MPI_Request * request = new MPI_Request(); - MPI_Isend( &(*m_send_buffers_ptr)[peID][0], - (*m_send_buffers_ptr)[peID].size(), - MPI_UNSIGNED_LONG_LONG, - peID, m_send_tag, m_communicator, request); - - m_isend_requests.push_back( request ); - } - } - - m_first_send = false; - if( m_send_buffers_ptr == & m_send_buffers_A) { - m_send_buffers_ptr = & m_send_buffers_B; - } else { - m_send_buffers_ptr = & m_send_buffers_A; - } - return; // compute a little bit more - } - - m_G->update_block_weights(); - - //receive incomming - receive_messages_of_neighbors(); - - if( m_send_buffers_ptr == & m_send_buffers_A) { - for( int i = 0; i < m_size; i++) { - m_send_buffers_B[i].clear(); - } - } else { - for( int i = 0; i < m_size; i++) { - m_send_buffers_A[i].clear(); - } - } - - for( PEID peID = 0; peID < (PEID)(*m_send_buffers_ptr).size(); peID++) { - if( m_adjacent_processors[peID] ) { - //now we have to send a message - if( (*m_send_buffers_ptr)[peID].size() == 0 ){ - // length 1 encode no message - (*m_send_buffers_ptr)[peID].push_back(0); - } - - MPI_Request * request = new MPI_Request(); - MPI_Isend( &(*m_send_buffers_ptr)[peID][0], - (*m_send_buffers_ptr)[peID].size(), - MPI_UNSIGNED_LONG_LONG, - peID, m_send_tag, m_communicator, request); - - m_isend_requests.push_back( request ); - } +// Modern and safe graph traversal functions +// Function to iterate over all local nodes +template +void for_all_local_nodes(parallel_graph_access& G, Func func) { + for (NodeID n = 0; n < G.number_of_local_nodes(); ++n) { + func(n); } - - // switch send buffers - if( m_send_buffers_ptr == & m_send_buffers_A) { - m_send_buffers_ptr = & m_send_buffers_B; - } else { - m_send_buffers_ptr = & m_send_buffers_A; - } - } -inline -void ghost_node_communication::receive_messages_of_neighbors() { - PEID counter = 0; - m_recv_iteration++; - m_recv_tag++; - while( counter < m_num_adjacent ) { - // wait for incomming message of an adjacent processor - MPI_Status st; - MPI_Probe(MPI_ANY_SOURCE, m_recv_tag, m_communicator, &st); - - int message_length; - MPI_Get_count(&st, MPI_UNSIGNED_LONG_LONG, &message_length); - - std::vector message; message.resize(message_length); - MPI_Status rst; - MPI_Recv( &message[0], message_length, MPI_UNSIGNED_LONG_LONG, st.MPI_SOURCE, m_recv_tag, m_communicator, &rst); - - counter++; - - // now integrate the changes - if(message_length == 1) continue; // nothing to do - - for( int i = 0; i < message_length-1; i+=2) { - NodeID global_id = message[i]; - NodeID label = message[i+1]; - - NodeID local_id = m_G->m_global_to_local_id[global_id]; - m_G->update_non_contained_block_balance(m_G->getNodeLabel(local_id), label, m_G->getNodeWeight(local_id)); - m_G->setNodeLabel(local_id, label); - } +// Function to iterate over all ghost nodes +template +void for_all_ghost_nodes(parallel_graph_access& G, Func func) { + for (NodeID node = G.number_of_local_nodes()+1, end = G.number_of_local_nodes()+1+G.number_of_ghost_nodes(); node < end; ++node) { + func(node); } - - // wait for previous iteration to finish - for( unsigned i = 0; i < m_isend_requests.size(); i++) { - MPI_Status st; - MPI_Wait( m_isend_requests[i], &st); - delete m_isend_requests[i]; - } - m_isend_requests.clear(); - } -inline void ghost_node_communication::update_ghost_node_data_finish() { - while( m_send_iteration < m_desired_rounds) { - // we have to do another send - update_ghost_node_data( false ); // flush the lokal buffers to our neighbors - } - - while( m_recv_iteration < m_desired_rounds) { - receive_messages_of_neighbors(); // last receive - } - - m_first_send = true; // last send - update_ghost_node_data(false); - m_G->update_block_weights(); - receive_messages_of_neighbors(); - - m_send_iteration = 0; - m_recv_iteration = 0; - - m_send_tag = 100*m_size-1; - m_recv_tag = 100*m_size-1; - m_first_send = true; - - for( int i = 0; i < m_size; i++) { - m_send_buffers_B[i].clear(); +// Function to iterate over all local edges +template +void for_all_local_edges(parallel_graph_access& G, Func func) { + for (EdgeID e = 0; e < G.number_of_local_edges(); ++e) { + func(e); } - - for( int i = 0; i < m_size; i++) { - m_send_buffers_A[i].clear(); - } - - MPI_Barrier(m_communicator); - } -inline void ghost_node_communication::update_ghost_node_data_global() { - std::vector< std::vector< NodeID > > send_buffers; // buffers to send messages - send_buffers.resize(m_size); - forall_local_nodes((*m_G), node) { - forall_out_edges((*m_G), e, node) { - NodeID target = m_G->getEdgeTarget(e); - if( !m_G->is_local_node(target) ) { - PEID peID = m_G->getTargetPE(target); - if( !m_PE_packed[peID] ) { // make sure a node is sent at most once - send_buffers[peID].push_back(m_G->getGlobalID(node)); - send_buffers[peID].push_back(m_G->getNodeLabel(node)); - m_PE_packed[peID] = true; - } - } - } endfor - forall_out_edges((*m_G), e, node) { - NodeID target = m_G->getEdgeTarget(e); - if( !m_G->is_local_node(target) ) { - m_PE_packed[m_G->getTargetPE(target)] = false; - } - } endfor - } endfor - - //send all neighbors their packages using Isends - //a neighbor that does not receive something gets a specific token - for( PEID peID = 0; peID < (PEID)send_buffers.size(); peID++) { - if( m_adjacent_processors[peID] ) { - //now we have to send a message - if( send_buffers[peID].size() == 0 ){ - // length 1 encode no message - send_buffers[peID].push_back(0); - } - - MPI_Request rq; - MPI_Isend( &send_buffers[peID][0], - send_buffers[peID].size(), MPI_UNSIGNED_LONG_LONG, peID, peID+3*m_size, m_communicator, &rq); - } +// Function to iterate over all outgoing edges of a node +template +void for_all_out_edges(parallel_graph_access& G, NodeID n, Func func) { + for (EdgeID e = G.get_first_edge(n); e < G.get_first_invalid_edge(n); ++e) { + func(e); } - - //receive incomming - PEID counter = 0; - while( counter < m_num_adjacent ) { - // wait for incomming message of an adjacent processor - MPI_Status st; unsigned int tag = m_rank+3*m_size; - MPI_Probe(MPI_ANY_SOURCE, tag, m_communicator, &st); - - int message_length; - MPI_Get_count(&st, MPI_UNSIGNED_LONG_LONG, &message_length); - std::vector message; message.resize(message_length); - - MPI_Status rst; - MPI_Recv( &message[0], message_length, MPI_UNSIGNED_LONG_LONG, st.MPI_SOURCE, tag, m_communicator, &rst); - counter++; - - // now integrate the changes - if(message_length == 1) continue; // nothing to do - - for( int i = 0; i < message_length-1; i+=2) { - NodeID global_id = message[i]; - NodeID label = message[i+1]; - - m_G->setNodeLabel( m_G->m_global_to_local_id[global_id], label); - } - } - - MPI_Barrier(m_communicator); } +} #endif /* end of include guard: PARALLEL_GRAPH_ACCESS_X6O9MRS8 */ diff --git a/parallel/parallel_src/lib/definitions.h b/parallel/parallel_src/lib/definitions.h index 142e843d..c0d2114f 100644 --- a/parallel/parallel_src/lib/definitions.h +++ b/parallel/parallel_src/lib/definitions.h @@ -7,14 +7,12 @@ #ifndef DEFINITIONS_H_CHRA #define DEFINITIONS_H_CHRA +#include -#include -#include -#include +#include +#include "macros_assertions.h" +#include -#include "limits.h" -#include "macros_assertions.h" -#include "stdio.h" // allows us to disable most of the output during partitioning #ifndef NOOUTPUT @@ -23,29 +21,30 @@ #define PRINT(x) do {} while (false); #endif +namespace parhip { /********************************************** * Constants * ********************************************/ //Types needed for the parallel graph ds //we use long since we want to partition huge graphs -typedef unsigned long long ULONG; -typedef unsigned int UINT; -typedef unsigned long long NodeID; -typedef unsigned long long EdgeID; -typedef unsigned long long PartitionID; -typedef unsigned long long NodeWeight; -typedef unsigned long long EdgeWeight; -typedef int PEID; +using ULONG = unsigned long long; +using UINT = unsigned int; +using NodeID = unsigned long long; +using EdgeID = unsigned long long; +using PartitionID = unsigned long long; +using NodeWeight = unsigned long long; +using EdgeWeight = unsigned long long; +using PEID = int; -const PEID ROOT = 0; +constexpr PEID ROOT = 0; -typedef enum { +enum class PermutationQuality : std::uint8_t { PERMUTATION_QUALITY_NONE, - PERMUTATION_QUALITY_FAST, - PERMUTATION_QUALITY_GOOD -} PermutationQuality; + PERMUTATION_QUALITY_FAST, + PERMUTATION_QUALITY_GOOD +}; -typedef enum { +enum class InitialPartitioningAlgorithm : std::uint8_t { KAFFPAESTRONG, KAFFPAEECO, KAFFPAEFAST, @@ -54,21 +53,20 @@ typedef enum { KAFFPAEECOSNW, KAFFPAESTRONGSNW, RANDOMIP -} InitialPartitioningAlgorithm; +}; struct source_target_pair { NodeID source; NodeID target; }; -typedef enum { - RANDOM_NODEORDERING, +enum class NodeOrderingType : std::uint8_t { + RANDOM_NODEORDERING, DEGREE_NODEORDERING, - LEASTGHOSTNODESFIRST_DEGREE_NODEODERING, - DEGREE_LEASTGHOSTNODESFIRST_NODEODERING -} NodeOrderingType; - - + LEASTGHOSTNODESFIRST_DEGREE_NODEODERING, + DEGREE_LEASTGHOSTNODESFIRST_NODEODERING +}; +} #endif //Tag Listing of Isend Operations(they should be unique per level) diff --git a/parallel/parallel_src/lib/distributed_partitioning/distributed_consistency.h b/parallel/parallel_src/lib/distributed_partitioning/distributed_consistency.h new file mode 100644 index 00000000..23be66d9 --- /dev/null +++ b/parallel/parallel_src/lib/distributed_partitioning/distributed_consistency.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +#include "communication/mpi_types.h" +#include "definitions.h" + +namespace parhip::distributed_consistency { +struct node_value { + NodeID global_id; + NodeID value; + + auto operator==(node_value const&) const -> bool = default; +}; + +static_assert(std::is_standard_layout_v); +static_assert(std::is_trivially_copyable_v); +} // namespace parhip::distributed_consistency + +template <> +struct parhip::mpi::wire_members { + inline static constexpr auto value = std::tuple{ + &parhip::distributed_consistency::node_value::global_id, + &parhip::distributed_consistency::node_value::value}; +}; diff --git a/parallel/parallel_src/lib/distributed_partitioning/distributed_partitioner.cpp b/parallel/parallel_src/lib/distributed_partitioning/distributed_partitioner.cpp index d487f734..aa78b6a2 100644 --- a/parallel/parallel_src/lib/distributed_partitioning/distributed_partitioner.cpp +++ b/parallel/parallel_src/lib/distributed_partitioning/distributed_partitioner.cpp @@ -5,8 +5,26 @@ * Christian Schulz *****************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include #include -#include "communication/mpi_tools.h" +#include +#include +#include +#include + +#include "communication/ghost_exchange_plan.h" +#include "communication/mpi_collectives.h" +#include "communication/mpi_failure.h" +#include "communication/mpi_neighbors.h" +#include "communication/mpi_trace.h" +#include "distributed_partitioning/distributed_consistency.h" #include "distributed_partitioner.h" #include "initial_partitioning/initial_partitioning.h" #include "io/parallel_graph_io.h" @@ -18,417 +36,623 @@ #include "tools/distributed_quality_metrics.h" #include "tools/random_functions.h" #include "data_structure/linear_probing_hashmap.h" +namespace parhip { +namespace { +void require_collectively(bool local_condition, + mpi::communicator_view communicator, + std::string_view diagnostic) noexcept { + if (!mpi::detail::collective_predicate(local_condition, communicator)) { + mpi::abort_on_programming_error(communicator.native_handle(), diagnostic); + } +} -std::vector< NodeID > distributed_partitioner::m_cf = std::vector< NodeID >(); -std::vector< NodeID > distributed_partitioner::m_sf = std::vector< NodeID >(); -std::vector< NodeID > distributed_partitioner::m_lic = std::vector< NodeID >(); +void require_matching_int(int value, + mpi::communicator_view communicator, + std::string_view diagnostic) noexcept { + auto minimum = 0; + auto maximum = 0; + mpi::check_or_abort( + MPI_Allreduce(&value, &minimum, 1, MPI_INT, MPI_MIN, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(distributed partitioner integer minimum)"); + mpi::check_or_abort( + MPI_Allreduce(&value, &maximum, 1, MPI_INT, MPI_MAX, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(distributed partitioner integer maximum)"); + if (minimum != maximum) { + mpi::abort_on_programming_error(communicator.native_handle(), diagnostic); + } +} -distributed_partitioner::distributed_partitioner() { - m_total_graph_weight = std::numeric_limits< NodeWeight >::max(); - m_cur_rnd_choice = 0; - m_level = -1; - m_cycle = 0; +void require_matching_block_count(PartitionID block_count, + mpi::communicator_view communicator) noexcept { + static_assert(sizeof(PartitionID) <= sizeof(std::uint64_t)); + auto const local = static_cast(block_count); + auto minimum = std::uint64_t{}; + auto maximum = std::uint64_t{}; + mpi::check_or_abort( + MPI_Allreduce(&local, &minimum, 1, MPI_UINT64_T, MPI_MIN, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(distributed partitioner k minimum)"); + mpi::check_or_abort( + MPI_Allreduce(&local, &maximum, 1, MPI_UINT64_T, MPI_MAX, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(distributed partitioner k maximum)"); + if (minimum != maximum) { + mpi::abort_on_programming_error( + communicator.native_handle(), + "distributed partitioner k differs across communicator"); + } + if (block_count == 0) { + mpi::abort_on_programming_error( + communicator.native_handle(), + "distributed partitioner requires k greater than zero"); + } } -distributed_partitioner::~distributed_partitioner() { +void require_compatible_graph_communicator( + mpi::communicator_view communicator, + parallel_graph_access& graph) noexcept { + auto const graph_communicator = graph.getCommunicator(); + require_collectively( + graph_communicator != MPI_COMM_NULL, communicator, + "distributed partitioning requires a live graph communicator"); + + auto relation = int{MPI_UNEQUAL}; + mpi::check_or_abort( + MPI_Comm_compare(communicator.native_handle(), graph_communicator, + &relation), + communicator.native_handle(), + "MPI_Comm_compare(distributed partitioning graph)"); + require_collectively( + relation == MPI_IDENT || relation == MPI_CONGRUENT, communicator, + "distributed partitioning graph communicator differs in process or " + "rank order"); } -void distributed_partitioner::generate_random_choices( PPartitionConfig & config ){ - for( int i = 0; i < config.num_tries; i++) { - for( int j = 0; j < config.num_vcycles; j++) { - m_cf.push_back(random_functions::nextDouble( 10, 25 )); - m_sf.push_back(random_functions::nextInt( 20, 500 )); - m_lic.push_back(random_functions::nextInt( 2, 15 )); - } - } +void require_valid_partition_config(PPartitionConfig const& config, + mpi::communicator_view communicator) noexcept { + require_matching_block_count(config.k, communicator); + require_matching_int( + config.num_vcycles, communicator, + "distributed partitioner vcycle count differs across communicator"); + require_collectively( + config.num_vcycles >= 0, communicator, + "distributed partitioner requires a nonnegative vcycle count"); + require_matching_int( + config.eco ? 1 : 0, communicator, + "distributed partitioner eco mode differs across communicator"); + + auto const factor_is_valid = + std::isfinite(config.cluster_coarsening_factor) && + config.cluster_coarsening_factor > 0.0; + require_collectively( + factor_is_valid, communicator, + "distributed partitioner requires a finite positive cluster " + "coarsening factor"); + auto minimum_factor = 0.0; + auto maximum_factor = 0.0; + mpi::check_or_abort( + MPI_Allreduce(&config.cluster_coarsening_factor, &minimum_factor, 1, + MPI_DOUBLE, MPI_MIN, communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(distributed partitioner cluster factor minimum)"); + mpi::check_or_abort( + MPI_Allreduce(&config.cluster_coarsening_factor, &maximum_factor, 1, + MPI_DOUBLE, MPI_MAX, communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(distributed partitioner cluster factor maximum)"); + if (minimum_factor != maximum_factor) { + mpi::abort_on_programming_error( + communicator.native_handle(), + "distributed partitioner cluster coarsening factor differs across " + "communicator"); + } } -void distributed_partitioner::perform_recursive_partitioning( PPartitionConfig & partition_config, parallel_graph_access & G) { - perform_partitioning( MPI_COMM_WORLD, partition_config, G); +[[nodiscard]] auto validated_random_choice_count( + PPartitionConfig const& config, + mpi::communicator_view communicator) noexcept -> std::size_t { + require_matching_int( + config.num_tries, communicator, + "distributed partitioner try count differs across communicator"); + require_matching_int( + config.num_vcycles, communicator, + "distributed partitioner vcycle count differs across communicator"); + require_collectively( + config.num_tries >= 0 && config.num_vcycles >= 0, communicator, + "distributed partitioner random-choice counts must be nonnegative"); + + auto const tries = static_cast(config.num_tries); + auto const cycles = static_cast(config.num_vcycles); + auto const product_is_representable = + tries == 0 || cycles <= std::numeric_limits::max() / tries; + require_collectively( + product_is_representable, communicator, + "distributed partitioner random-choice count arithmetic overflow"); + auto const count = tries * cycles; + auto const maximum_count = std::vector{}.max_size(); + if (!mpi::detail::collective_predicate(count <= maximum_count, + communicator)) { + mpi::abort_on_capacity_failure( + communicator.native_handle(), "distributed random-choice generation", + "random-choice count exceeds addressable vector capacity"); + } + return count; } -void distributed_partitioner::perform_recursive_partitioning( MPI_Comm communicator, PPartitionConfig & partition_config, parallel_graph_access & G) { +[[nodiscard]] auto cluster_upper_bound(PPartitionConfig const& config) noexcept + -> NodeWeight { + if (config.cluster_coarsening_factor <= 1.0) { + return config.upper_bound_partition; + } + auto const scaled = static_cast( + static_cast(config.upper_bound_partition) / + config.cluster_coarsening_factor); + auto const preferred = config.cluster_coarsening_factor > 100.0 + ? std::max(NodeWeight{100}, scaled) + : scaled; + return std::min(config.upper_bound_partition, preferred); } +template +void validate_distributed_node_values( + MPI_Comm communicator, + parallel_graph_access& graph, + ProjectValue project_value, + ReadGhostValue read_ghost_value, + std::string_view failure_context) { + auto const graph_communicator = + mpi::communicator_view{graph.getCommunicator()}; + auto communicator_is_compatible = communicator != MPI_COMM_NULL; + if (communicator_is_compatible) { + auto comparison = int{MPI_UNEQUAL}; + mpi::check_or_abort( + MPI_Comm_compare(communicator, graph.getCommunicator(), &comparison), + graph.getCommunicator(), + "MPI_Comm_compare(distributed consistency)"); + communicator_is_compatible = + comparison == MPI_IDENT || comparison == MPI_CONGRUENT; + } + if (!mpi::detail::collective_predicate(communicator_is_compatible, + graph_communicator)) { + mpi::detail::throw_collectively_agreed_semantic_error_from( + graph.getCommunicator(), [&] { + return mpi::mpi_error{ + MPI_ERR_COMM, + std::string{failure_context} + + " communicator validation failed"}; + }); + } + + auto const& plan = graph.ghost_plan(); + auto semantic_failure = false; + try { + auto outgoing = + std::vector>( + plan.topology().destinations().size()); + for (std::size_t destination_index = 0; + destination_index < plan.topology().destinations().size(); + ++destination_index) { + auto const local_nodes = + plan.outgoing_local_nodes(destination_index); + auto& records = outgoing[destination_index]; + records.reserve(local_nodes.size()); + std::ranges::transform( + local_nodes, + std::back_inserter(records), + [&](NodeID const local_node) { + return distributed_consistency::node_value{ + graph.getGlobalID(local_node), + project_value(graph, local_node)}; + }); + } + + auto received = mpi::neighbor_all_to_all_v( + mpi::segmented_buffer:: + from_segments(outgoing), + plan.topology()); + + auto resolved_local_ids = + std::vector>(plan.topology().sources().size()); + auto local_structure_is_valid = + received.segment_count() == plan.topology().sources().size(); + for (std::size_t source_index = 0; + source_index < plan.topology().sources().size(); ++source_index) { + auto const source = plan.topology().sources()[source_index]; + auto const records = received.segment(source_index); + auto const expected = plan.expected_ghost_nodes(source_index); + auto& local_ids = resolved_local_ids[source_index]; + local_ids.reserve(records.size()); + auto received_ids = std::vector{}; + received_ids.reserve(records.size()); + for (auto const& record : records) { + received_ids.push_back(record.global_id); + auto const local_id = + graph.find_ghost_local_id(record.global_id, source); + local_structure_is_valid = + local_structure_is_valid && local_id.has_value(); + local_ids.push_back(local_id.value_or(NodeID{0})); + } + std::ranges::sort(received_ids); + local_structure_is_valid = + local_structure_is_valid && records.size() == expected.size() && + std::ranges::adjacent_find(received_ids) == received_ids.end() && + std::ranges::equal(received_ids, expected); + } + + auto const structure_is_valid = mpi::detail::collective_predicate( + local_structure_is_valid, plan.topology().view()); + if (!structure_is_valid) { + semantic_failure = true; + } else { + auto local_values_are_valid = true; + for (std::size_t source_index = 0; + source_index < plan.topology().sources().size(); ++source_index) { + auto const records = received.segment(source_index); + auto const& local_ids = resolved_local_ids[source_index]; + for (std::size_t record_index = 0; + record_index < records.size(); ++record_index) { + local_values_are_valid = + local_values_are_valid && + read_ghost_value(graph, local_ids[record_index]) == + records[record_index].value; + } + } + semantic_failure = !mpi::detail::collective_predicate( + local_values_are_valid, plan.topology().view()); + } + } catch (...) { + mpi::abort_on_exception(plan.topology().native_handle(), failure_context); + } + + if (semantic_failure) { + mpi::throw_collectively_agreed_semantic_error( + plan.topology().native_handle(), failure_context); + } +} +} // namespace + +std::vector< NodeID > distributed_partitioner::m_cf = std::vector< NodeID >(); +std::vector< NodeID > distributed_partitioner::m_sf = std::vector< NodeID >(); +std::vector< NodeID > distributed_partitioner::m_lic = std::vector< NodeID >(); -void distributed_partitioner::perform_partitioning( PPartitionConfig & partition_config, parallel_graph_access & G) { - perform_partitioning( MPI_COMM_WORLD, partition_config, G); +distributed_partitioner::distributed_partitioner() { + m_total_graph_weight = std::numeric_limits< NodeWeight >::max(); + m_cur_rnd_choice = 0; + m_level = -1; + m_cycle = 0; } -void distributed_partitioner::perform_partitioning( MPI_Comm communicator, PPartitionConfig & partition_config, parallel_graph_access & G) { - timer t; - double elapsed = 0; - m_cur_rnd_choice = 0; - PPartitionConfig config = partition_config; - config.vcycle = false; - - PEID rank; - MPI_Comm_rank( communicator, &rank); - - for( int cycle = 0; cycle < partition_config.num_vcycles; cycle++) { - t.restart(); - m_cycle = cycle; - - if(cycle+1 == partition_config.num_vcycles && partition_config.no_refinement_in_last_iteration) { - config.label_iterations_refinement = 0; - } - - vcycle( communicator, config, G ); - - if( rank == ROOT ) { - PRINT(std::cout << "log>cycle: " << m_cycle << " uncoarsening took " << m_t.elapsed() << std::endl;) - } +distributed_partitioner::~distributed_partitioner() { +} + +void distributed_partitioner::generate_random_choices( + PPartitionConfig& config, + mpi::communicator_view communicator) { + mpi::require_live_intracommunicator( + communicator, + "distributed random-choice generation requires a live " + "intracommunicator"); + auto const rank = communicator.rank(); + auto const size = communicator.size(); + require_collectively( + size > 0 && rank >= 0 && rank < size, communicator, + "distributed random-choice generation received an invalid communicator " + "rank or size"); + auto const choice_count = validated_random_choice_count(config, communicator); + + try { + auto contraction_factors = std::vector{}; + auto stop_factors = std::vector{}; + auto label_iteration_counts = std::vector{}; + contraction_factors.reserve(choice_count); + stop_factors.reserve(choice_count); + label_iteration_counts.reserve(choice_count); + for (auto attempt = 0; attempt < config.num_tries; ++attempt) { + for (auto cycle = 0; cycle < config.num_vcycles; ++cycle) { + contraction_factors.push_back( + static_cast(random_functions::nextDouble(10, 25))); + stop_factors.push_back(random_functions::nextInt(20, 500)); + label_iteration_counts.push_back(random_functions::nextInt(2, 15)); + } + } + m_cf.swap(contraction_factors); + m_sf.swap(stop_factors); + m_lic.swap(label_iteration_counts); + } catch (...) { + mpi::abort_on_exception(communicator.native_handle(), + "distributed random-choice generation failed"); + } +} + +void distributed_partitioner::perform_partitioning( + MPI_Comm communicator, + PPartitionConfig& partition_config, + parallel_graph_access& G) { + auto const communicator_view = mpi::communicator_view{communicator}; + mpi::require_live_intracommunicator( + communicator_view, + "distributed partitioning requires a live intracommunicator"); + auto const rank = communicator_view.rank(); + auto const size = communicator_view.size(); + require_collectively( + size > 0 && rank >= 0 && rank < size, communicator_view, + "distributed partitioning received an invalid communicator rank or " + "size"); + require_compatible_graph_communicator(communicator_view, G); + require_valid_partition_config(partition_config, communicator_view); + if (partition_config.eco) { + auto const required_choices = + static_cast(partition_config.num_vcycles); + require_collectively( + m_cf.size() >= required_choices, communicator_view, + "distributed partitioning random-choice cursor exceeds generated " + "choices"); + } + + try { + auto t = timer{}; + [[maybe_unused]] auto elapsed = 0.0; + m_cur_rnd_choice = 0; + auto config = partition_config; + config.vcycle = false; + + for (auto cycle = 0; cycle < partition_config.num_vcycles; ++cycle) { + t.restart(); + m_cycle = cycle; + + if (cycle + 1 == partition_config.num_vcycles && + partition_config.no_refinement_in_last_iteration) { + config.label_iterations_refinement = 0; + } + + vcycle(communicator_view, config, G); + + if (rank == ROOT) { + PRINT(std::cout << "log>cycle: " << m_cycle + << " uncoarsening took " << m_t.elapsed() + << std::endl;) + } #ifndef NDEBUG - check_labels(communicator, config, G); + check_labels(communicator, config, G); #endif - elapsed += t.elapsed(); + elapsed += t.elapsed(); #ifndef NOOUTPUT - distributed_quality_metrics qm; - EdgeWeight edge_cut = qm.edge_cut( G, communicator ); - double balance = qm.balance( config, G, communicator ); - - if( rank == ROOT ) { - std::cout << "log>cycle: " << cycle << " k " << config.k << " cut " << edge_cut << " balance " << balance << " time " << elapsed << std::endl; - } -#endif - t.restart(); - m_t.restart(); - if( cycle+1 < config.num_vcycles ) { - forall_local_nodes(G, node) { - G.setSecondPartitionIndex(node, G.getNodeLabel(node)); - G.setNodeLabel(node, G.getGlobalID(node)); - } endfor - - forall_ghost_nodes(G, node) { - G.setSecondPartitionIndex(node, G.getNodeLabel(node)); - G.setNodeLabel(node, G.getGlobalID(node)); - } endfor - } - - config.vcycle = true; - - if( rank == ROOT && config.eco ) { - config.cluster_coarsening_factor = m_cf[m_cur_rnd_choice++]; - } - - if(config.eco) { - MPI_Bcast(&(config.cluster_coarsening_factor), 1, MPI_DOUBLE, ROOT, communicator); - - //std::cout << "cf " << config.cluster_coarsening_factor << std::endl; - } - config.evolutionary_time_limit = 0; - elapsed += t.elapsed(); - MPI_Barrier(communicator); - + auto qm = distributed_quality_metrics{}; + auto const edge_cut = qm.edge_cut(G, communicator); + auto const balance = qm.balance(config, G, communicator); + + if (rank == ROOT) { + std::cout << "log>cycle: " << cycle << " k " << config.k << " cut " + << edge_cut << " balance " << balance << " time " << elapsed + << '\n'; + } +#endif + t.restart(); + m_t.restart(); + if (cycle + 1 < config.num_vcycles) { + forall_local_nodes(G, node) { + G.setSecondPartitionIndex(node, G.getNodeLabel(node)); + G.setNodeLabel(node, G.getGlobalID(node)); + } endfor + + forall_ghost_nodes(G, node) { + G.setSecondPartitionIndex(node, G.getNodeLabel(node)); + G.setNodeLabel(node, G.getGlobalID(node)); + } endfor + } + + config.vcycle = true; + + if (rank == ROOT && config.eco) { + if (m_cur_rnd_choice >= m_cf.size()) { + mpi::abort_on_programming_error( + communicator, + "distributed partitioning random-choice cursor escaped its " + "validated range"); } + config.cluster_coarsening_factor = + static_cast(m_cf[m_cur_rnd_choice++]); + } + + if (config.eco) { + mpi::check_or_abort( + MPI_Bcast(&config.cluster_coarsening_factor, 1, MPI_DOUBLE, ROOT, + communicator), + communicator, + "MPI_Bcast(distributed partitioner cluster coarsening factor)"); + } + config.evolutionary_time_limit = 0; + elapsed += t.elapsed(); + mpi::check_or_abort(MPI_Barrier(communicator), communicator, + "MPI_Barrier(distributed partitioning cycle)"); + } + } catch (...) { + mpi::abort_on_exception(communicator, + "distributed partitioning failed"); + } } -void distributed_partitioner::vcycle( MPI_Comm communicator, PPartitionConfig & partition_config, parallel_graph_access & G) { - PPartitionConfig config = partition_config; +void distributed_partitioner::vcycle( + mpi::communicator_view communicator_view, + PPartitionConfig& partition_config, + parallel_graph_access& G) { + auto const communicator = communicator_view.native_handle(); + auto config = partition_config; + auto t = timer{}; - mpi_tools mpitools; - timer t; + if( m_total_graph_weight == std::numeric_limits< NodeWeight >::max() ) { + m_total_graph_weight = G.number_of_global_nodes(); + } - if( m_total_graph_weight == std::numeric_limits< NodeWeight >::max() ) { - m_total_graph_weight = G.number_of_global_nodes(); - } + [[maybe_unused]] auto const rank = communicator_view.rank(); - PEID rank; - MPI_Comm_rank( communicator, &rank); - #ifndef NOOUTPUT - if( rank == ROOT ) { - std::cout << "log>" << "=====================================" << std::endl; - std::cout << "log>" << "=============NEXT LEVEL==============" << std::endl; - std::cout << "log>" << "=====================================" << std::endl; - } + if( rank == ROOT ) { + std::cout << "log>" << "=====================================" << std::endl; + std::cout << "log>" << "=============NEXT LEVEL==============" << std::endl; + std::cout << "log>" << "=====================================" << std::endl; + } #endif - t.restart(); - - - m_level++; - config.label_iterations = config.label_iterations_coarsening; - config.total_num_labels = G.number_of_global_nodes(); - // - if( config.cluster_coarsening_factor > 100 ) { - config.upper_bound_cluster = std::max(100, (int)(config.upper_bound_partition/(1.0*config.cluster_coarsening_factor))); - } else { - config.upper_bound_cluster = (int)(config.upper_bound_partition/(1.0*config.cluster_coarsening_factor)); - } - G.init_balance_management( config ); + t.restart(); + - //parallel_label_compress< std::unordered_map< NodeID, NodeWeight> > plc; - parallel_label_compress< linear_probing_hashmap > plc; - plc.perform_parallel_label_compression ( config, G, true); + m_level++; + KAHIP_MPI_TRACE_SET_HIERARCHY( + m_cycle, m_level, mpi::trace::epoch::coarsening); + config.label_iterations = config.label_iterations_coarsening; + config.total_num_labels = G.number_of_global_nodes(); + // + // A contracted vertex cannot be split by the initial partitioner. Never + // create a cluster that is already too heavy for every legal output block. + config.upper_bound_cluster = cluster_upper_bound(config); + G.init_balance_management( config ); + + //parallel_label_compress< std::unordered_map< NodeID, NodeWeight> > plc; + parallel_label_compress< linear_probing_hashmap > plc; + plc.perform_parallel_label_compression ( config, G, true); #ifndef NOOUTPUT - if( rank == ROOT ) { - std::cout << "log>cycle: " << m_cycle << " level: " << m_level << " parallel label compression took " << t.elapsed() << std::endl; - } + if( rank == ROOT ) { + std::cout << "log>cycle: " << m_cycle << " level: " << m_level << " parallel label compression took " << t.elapsed() << std::endl; + } #endif - parallel_graph_access Q(communicator); - t.restart(); + parallel_graph_access Q(communicator); + t.restart(); - { - parallel_contraction parallel_contract; - parallel_contract.contract_to_distributed_quotient( communicator, config, G, Q); // contains one Barrier + { + KAHIP_MPI_TRACE_SET_HIERARCHY( + m_cycle, m_level, mpi::trace::epoch::contraction); + parallel_contraction parallel_contract; + parallel_contract.contract_to_distributed_quotient( communicator, config, G, Q); // contains one Barrier + + parallel_block_down_propagation pbdp; + if( config.vcycle ) { + // in this case we have to propagate the partitionindex down + pbdp.propagate_block_down( communicator, config, G, Q); + } + + mpi::check_or_abort(MPI_Barrier(communicator), communicator, + "MPI_Barrier(distributed quotient contraction)"); + } - parallel_block_down_propagation pbdp; - if( config.vcycle ) { - // in this case we have to propagate the partitionindex down - pbdp.propagate_block_down( communicator, config, G, Q); - } - - MPI_Barrier(communicator); - } - #ifndef NOOUTPUT - if( rank == ROOT ) { - std::cout << "log>cycle: " << m_cycle << " level: " << m_level << " contraction took " << t.elapsed() << std::endl; - std::cout << "log>cycle: " << m_cycle << " level: " << m_level << " coarse nodes n=" << Q.number_of_global_nodes() << ", coarse edges m=" << Q.number_of_global_edges() << std::endl; - } + if( rank == ROOT ) { + std::cout << "log>cycle: " << m_cycle << " level: " << m_level << " contraction took " << t.elapsed() << std::endl; + std::cout << "log>cycle: " << m_cycle << " level: " << m_level << " coarse nodes n=" << Q.number_of_global_nodes() << ", coarse edges m=" << Q.number_of_global_edges() << std::endl; + } #endif - if( !contraction_stop_decision.contraction_stop(config, G, Q)) { - vcycle( communicator, config, Q); - } else { + if( !contraction_stop_decision.contraction_stop(config, G, Q)) { + vcycle(communicator_view, config, Q); + } else { #ifndef NOOUTPUT - if( rank == ROOT ) { - std::cout << "log>" << "=====================================" << std::endl; - std::cout << "log>" << "================ IP =================" << std::endl; - std::cout << "log>" << "=====================================" << std::endl; - std::cout << "log>cycle: " << m_cycle << " total number of levels " << (m_level+1) << std::endl; - std::cout << "log>cycle: " << m_cycle << " number of coarsest nodes " << Q.number_of_global_nodes() << std::endl; - std::cout << "log>cycle: " << m_cycle << " number of coarsest edges " << Q.number_of_global_edges() << std::endl; - std::cout << "log>cycle: " << m_cycle << " coarsening took " << m_t.elapsed() << std::endl; - } + if( rank == ROOT ) { + std::cout << "log>" << "=====================================" << std::endl; + std::cout << "log>" << "================ IP =================" << std::endl; + std::cout << "log>" << "=====================================" << std::endl; + std::cout << "log>cycle: " << m_cycle << " total number of levels " << (m_level+1) << std::endl; + std::cout << "log>cycle: " << m_cycle << " number of coarsest nodes " << Q.number_of_global_nodes() << std::endl; + std::cout << "log>cycle: " << m_cycle << " number of coarsest edges " << Q.number_of_global_edges() << std::endl; + std::cout << "log>cycle: " << m_cycle << " coarsening took " << m_t.elapsed() << std::endl; + } #endif - t.restart(); + t.restart(); - initial_partitioning_algorithm ip; - ip.perform_partitioning( communicator, config, Q ); + KAHIP_MPI_TRACE_SET_HIERARCHY( + m_cycle, m_level, mpi::trace::epoch::initial_partition); + initial_partitioning_algorithm ip; + ip.perform_partitioning( communicator, config, Q ); #ifndef NOOUTPUT - if( rank == ROOT ) { - std::cout << "log>cycle: " << m_cycle << " initial partitioning took " << t.elapsed() << std::endl; - } - m_t.restart(); + if( rank == ROOT ) { + std::cout << "log>cycle: " << m_cycle << " initial partitioning took " << t.elapsed() << std::endl; + } + m_t.restart(); #endif - } + } #ifndef NOOUTPUT - if( rank == ROOT ) { - std::cout << "log>" << "=====================================" << std::endl; - std::cout << "log>" << "============PREV LEVEL ==============" << std::endl; - std::cout << "log>" << "=====================================" << std::endl; - } + if( rank == ROOT ) { + std::cout << "log>" << "=====================================" << std::endl; + std::cout << "log>" << "============PREV LEVEL ==============" << std::endl; + std::cout << "log>" << "=====================================" << std::endl; + } #endif - t.restart(); - parallel_projection parallel_project; - parallel_project.parallel_project( communicator, G, Q ); // contains a Barrier + t.restart(); + KAHIP_MPI_TRACE_SET_HIERARCHY( + m_cycle, m_level, mpi::trace::epoch::projection); + parallel_projection parallel_project; + parallel_project.parallel_project( communicator, G, Q ); // contains a Barrier #ifndef NOOUTPUT - if( rank == ROOT ) { - std::cout << "log>cycle: " << m_cycle << " level: " << m_level << " projection took " << t.elapsed() << std::endl; - } + if( rank == ROOT ) { + std::cout << "log>cycle: " << m_cycle << " level: " << m_level << " projection took " << t.elapsed() << std::endl; + } #endif - t.restart(); - config.label_iterations = config.label_iterations_refinement; + t.restart(); + config.label_iterations = config.label_iterations_refinement; - if( config.label_iterations != 0 ) { - config.total_num_labels = config.k; - config.upper_bound_cluster = config.upper_bound_partition; + if( config.label_iterations != 0 ) { + KAHIP_MPI_TRACE_SET_HIERARCHY( + m_cycle, m_level, mpi::trace::epoch::refinement); + config.total_num_labels = config.k; + config.upper_bound_cluster = config.upper_bound_partition; - G.init_balance_management( config ); - PPartitionConfig working_config = config; - working_config.vcycle = false; // assure that we actually can improve the cut + G.init_balance_management( config ); + PPartitionConfig working_config = config; + working_config.vcycle = false; // assure that we actually can improve the cut - parallel_label_compress< std::vector< NodeWeight> > plc_refinement; - plc_refinement.perform_parallel_label_compression( working_config, G, false, false); - } + parallel_label_compress< std::vector< NodeWeight> > plc_refinement; + plc_refinement.perform_parallel_label_compression( working_config, G, false, false); + } #ifndef NOOUTPUT - if( rank == ROOT ) { - std::cout << "log>cycle: " << m_cycle <<" level: " << m_level << " label compression refinement took " << t.elapsed() << std::endl; - } + if( rank == ROOT ) { + std::cout << "log>cycle: " << m_cycle <<" level: " << m_level << " label compression refinement took " << t.elapsed() << std::endl; + } #endif - m_level--; + m_level--; } void distributed_partitioner::check_labels( MPI_Comm communicator, PPartitionConfig & config, parallel_graph_access & G) { - PEID m_rank, m_size; - MPI_Comm_rank( communicator, &m_rank); - MPI_Comm_size( communicator, &m_size); - - std::vector< std::vector< NodeID > > send_buffers; // buffers to send messages - send_buffers.resize(m_size); - std::vector m_PE_packed; - m_PE_packed.resize(m_size); - for( unsigned peID = 0; peID < m_PE_packed.size(); peID++) { - m_PE_packed[ peID ] = false; - } - - forall_local_nodes(G, node) { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if( !G.is_local_node(target) ) { - PEID peID = G.getTargetPE(target); - if( !m_PE_packed[peID] ) { // make sure a node is sent at most once - send_buffers[peID].push_back(G.getGlobalID(node)); - send_buffers[peID].push_back(G.getNodeLabel(node)); - m_PE_packed[peID] = true; - } - } - } endfor - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if( !G.is_local_node(target) ) { - m_PE_packed[G.getTargetPE(target)] = false; - } - } endfor - } endfor - - //send all neighbors their packages using Isends - //a neighbor that does not receive something gets a specific token - for( PEID peID = 0; peID < (PEID)send_buffers.size(); peID++) { - if( G.is_adjacent_PE(peID) ) { - //now we have to send a message - if( send_buffers[peID].size() == 0 ){ - // length 1 encode no message - send_buffers[peID].push_back(0); - } - - MPI_Request rq; int tag = peID+17*m_size; - MPI_Isend( &send_buffers[peID][0], - send_buffers[peID].size(), MPI_UNSIGNED_LONG_LONG, peID, tag, communicator, &rq); - - } - } - - //receive incomming - PEID counter = 0; - while( counter < G.getNumberOfAdjacentPEs()) { - // wait for incomming message of an adjacent processor - unsigned int tag = m_rank+17*m_size; - MPI_Status st; - MPI_Probe(MPI_ANY_SOURCE, tag, communicator, &st); - - int message_length; - MPI_Get_count(&st, MPI_UNSIGNED_LONG_LONG, &message_length); - - std::vector message; message.resize(message_length); - - MPI_Status rst; - MPI_Recv( &message[0], message_length, MPI_UNSIGNED_LONG_LONG, st.MPI_SOURCE, tag, communicator, &rst); - - counter++; - - // now integrate the changes - if(message_length == 1) continue; // nothing to do - - for( int i = 0; i < message_length-1; i+=2) { - NodeID global_id = message[i]; - NodeID label = message[i+1]; - - if(G.getNodeLabel(G.getLocalID(global_id)) != label) { - std::cout << "labels not ok" << std::endl; - exit(0); - } - } - } - - MPI_Barrier(communicator); + static_cast(config); + validate_distributed_node_values( + communicator, + G, + [](parallel_graph_access& graph, NodeID const local_node) { + return graph.getNodeLabel(local_node); + }, + [](parallel_graph_access& graph, NodeID const ghost_node) { + return graph.getNodeLabel(ghost_node); + }, + "label consistency validation failed"); } void distributed_partitioner::check( MPI_Comm communicator, PPartitionConfig & config, parallel_graph_access & G) { - PEID m_rank, m_size; - MPI_Comm_rank( communicator, &m_rank); - MPI_Comm_size( communicator, &m_size); - - std::vector< std::vector< NodeID > > send_buffers; // buffers to send messages - send_buffers.resize(m_size); - std::vector m_PE_packed; - m_PE_packed.resize(m_size); - for( unsigned peID = 0; peID < m_PE_packed.size(); peID++) { - m_PE_packed[ peID ] = false; - } - - forall_local_nodes(G, node) { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if( !G.is_local_node(target) ) { - PEID peID = G.getTargetPE(target); - if( !m_PE_packed[peID] ) { // make sure a node is sent at most once - send_buffers[peID].push_back(G.getGlobalID(node)); - send_buffers[peID].push_back(G.getSecondPartitionIndex(node)); - m_PE_packed[peID] = true; - } - } - } endfor - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if( !G.is_local_node(target) ) { - m_PE_packed[G.getTargetPE(target)] = false; - } - } endfor - } endfor - - //send all neighbors their packages using Isends - //a neighbor that does not receive something gets a specific token - for( PEID peID = 0; peID < (PEID)send_buffers.size(); peID++) { - if( G.is_adjacent_PE(peID) ) { - //now we have to send a message - if( send_buffers[peID].size() == 0 ){ - // length 1 encode no message - send_buffers[peID].push_back(0); - } - - MPI_Request rq; - MPI_Isend( &send_buffers[peID][0], - send_buffers[peID].size(), MPI_UNSIGNED_LONG_LONG, peID, peID+17*m_size, communicator, &rq); - } - } - - //receive incomming - PEID counter = 0; - while( counter < G.getNumberOfAdjacentPEs()) { - // wait for incomming message of an adjacent processor - unsigned int tag = m_rank+17*m_size; - MPI_Status st; - MPI_Probe(MPI_ANY_SOURCE, tag, communicator, &st); - - int message_length; - MPI_Get_count(&st, MPI_UNSIGNED_LONG_LONG, &message_length); - - std::vector message; message.resize(message_length); - - MPI_Status rst; - MPI_Recv( &message[0], message_length, MPI_UNSIGNED_LONG_LONG, st.MPI_SOURCE, tag, communicator, &rst); - - counter++; - - // now integrate the changes - if(message_length == 1) continue; // nothing to do - - for( int i = 0; i < message_length-1; i+=2) { - NodeID global_id = message[i]; - NodeID label = message[i+1]; - - if(G.getSecondPartitionIndex(G.getLocalID(global_id)) != label) { - std::cout << "second partition index weird" << std::endl; - exit(0); - } - } - } - - MPI_Barrier(communicator); + static_cast(config); + validate_distributed_node_values( + communicator, + G, + [](parallel_graph_access& graph, NodeID const local_node) { + return graph.getSecondPartitionIndex(local_node); + }, + [](parallel_graph_access& graph, NodeID const ghost_node) { + return graph.getSecondPartitionIndex(ghost_node); + }, + "second-partition consistency validation failed"); +} } - diff --git a/parallel/parallel_src/lib/distributed_partitioning/distributed_partitioner.h b/parallel/parallel_src/lib/distributed_partitioning/distributed_partitioner.h index cf5fe493..e59ca72f 100644 --- a/parallel/parallel_src/lib/distributed_partitioning/distributed_partitioner.h +++ b/parallel/parallel_src/lib/distributed_partitioning/distributed_partitioner.h @@ -8,31 +8,35 @@ #ifndef DISTRIBUTED_PARTITIONER_ZYL2XF6R #define DISTRIBUTED_PARTITIONER_ZYL2XF6R +#include #include + +#include "communication/mpi_handles.h" #include "partition_config.h" #include "data_structure/parallel_graph_access.h" #include "stop_rule.h" - +namespace parhip { class distributed_partitioner { public: distributed_partitioner(); virtual ~distributed_partitioner(); - void perform_partitioning( PPartitionConfig & config, parallel_graph_access & G); - void perform_recursive_partitioning( PPartitionConfig & config, parallel_graph_access & G); - void perform_partitioning( MPI_Comm comm, PPartitionConfig & partition_config, parallel_graph_access & G); - void perform_recursive_partitioning( MPI_Comm comm, PPartitionConfig & partition_config, parallel_graph_access & G); void check( MPI_Comm comm, PPartitionConfig & config, parallel_graph_access & G); void check_labels( MPI_Comm comm, PPartitionConfig & config, parallel_graph_access & G); - static void generate_random_choices( PPartitionConfig & config ) ; + static void generate_random_choices( + PPartitionConfig& config, + mpi::communicator_view communicator); private: - void vcycle( MPI_Comm communicator, PPartitionConfig & config, parallel_graph_access & G ); + void vcycle( + mpi::communicator_view communicator, + PPartitionConfig& config, + parallel_graph_access& G); stop_rule contraction_stop_decision; NodeWeight m_total_graph_weight; - NodeID m_cur_rnd_choice; + std::size_t m_cur_rnd_choice; static std::vector< NodeID > m_cf; static std::vector< NodeID > m_sf; @@ -41,6 +45,6 @@ class distributed_partitioner { int m_cycle; timer m_t; }; - +} #endif /* end of include guard: DISTRIBUTED_PARTITIONER_ZYL2XF6R */ diff --git a/parallel/parallel_src/lib/distributed_partitioning/initial_partitioning/distributed_evolutionary_partitioning.cpp b/parallel/parallel_src/lib/distributed_partitioning/initial_partitioning/distributed_evolutionary_partitioning.cpp index 805d44e5..d30b38cd 100644 --- a/parallel/parallel_src/lib/distributed_partitioning/initial_partitioning/distributed_evolutionary_partitioning.cpp +++ b/parallel/parallel_src/lib/distributed_partitioning/initial_partitioning/distributed_evolutionary_partitioning.cpp @@ -5,12 +5,84 @@ * Christian Schulz *****************************************************************************/ +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_fixed_broadcast.h" #include "communication/mpi_tools.h" #include "distributed_evolutionary_partitioning.h" #include "kaHIP_interface.h" +#include "kaHIP_evolutionary_interface_internal.h" #include "parallel_contraction_projection/parallel_projection.h" #include "io/parallel_graph_io.h" +#include "serial_kernel_bridge.h" #include "tools/distributed_quality_metrics.h" +namespace parhip { +namespace { +struct checked_serial_input final { + int node_count{}; + std::vector xadj; + std::vector adjncy; + std::vector node_weights; + std::vector edge_weights; + std::vector partition; + + [[nodiscard]] static auto from_graph(complete_graph_access& graph, + bool preserve_vcycle_labels) + -> checked_serial_input { + if (!std::in_range(graph.number_of_local_nodes()) || + !std::in_range(graph.number_of_local_edges())) { + throw std::overflow_error{"serial graph counts exceed int"}; + } + auto const nodes = static_cast(graph.number_of_local_nodes()); + auto const edges = static_cast(graph.number_of_local_edges()); + auto input = checked_serial_input{ + .node_count = static_cast(nodes), + .xadj = std::vector(nodes + 1), + .adjncy = std::vector(edges), + .node_weights = std::vector(nodes), + .edge_weights = std::vector(edges), + .partition = std::vector(nodes), + }; + for (std::size_t node = 0; node < nodes; ++node) { + auto const node_id = static_cast(node); + auto const first_edge = graph.get_first_edge(node_id); + auto const weight = graph.getNodeWeight(node_id); + if (!std::in_range(first_edge) || !std::in_range(weight) || + (preserve_vcycle_labels && + !std::in_range(graph.getSecondPartitionIndex(node_id)))) { + throw std::overflow_error{"serial graph node field exceeds int"}; + } + input.xadj[node] = static_cast(first_edge); + input.node_weights[node] = static_cast(weight); + if (preserve_vcycle_labels) { + input.partition[node] = + static_cast(graph.getSecondPartitionIndex(node_id)); + } + } + if (!std::in_range(graph.get_first_edge(static_cast(nodes)))) { + throw std::overflow_error{"serial CSR sentinel exceeds int"}; + } + input.xadj[nodes] = + static_cast(graph.get_first_edge(static_cast(nodes))); + for (std::size_t edge = 0; edge < edges; ++edge) { + auto const edge_id = static_cast(edge); + auto const target = graph.getEdgeTarget(edge_id); + auto const weight = graph.getEdgeWeight(edge_id); + if (!std::in_range(target) || !std::in_range(weight)) { + throw std::overflow_error{"serial graph edge field exceeds int"}; + } + input.adjncy[edge] = static_cast(target); + input.edge_weights[edge] = static_cast(weight); + } + return input; + } +}; +} // namespace distributed_evolutionary_partitioning::distributed_evolutionary_partitioning() { @@ -23,193 +95,193 @@ distributed_evolutionary_partitioning::~distributed_evolutionary_partitioning() void distributed_evolutionary_partitioning::perform_partitioning( MPI_Comm communicator, PPartitionConfig & config, parallel_graph_access & Q) { - mpi_tools mpitools; - parallel_graph_access Q_bar; - distributed_quality_metrics dqm; - mpitools.collect_parallel_graph_to_local_graph( communicator, config, Q, Q_bar); - mpitools.distribute_local_graph( communicator, config, Q_bar); - - - - int n = Q_bar.number_of_local_nodes(); - int nparts = config.k; // k-way partitioning. - - int* xadj = Q_bar.UNSAFE_metis_style_xadj_array(); - int* adjncy = Q_bar.UNSAFE_metis_style_adjncy_array(); - - int* vwgt = Q_bar.UNSAFE_metis_style_vwgt_array(); - int* adjwgt = Q_bar.UNSAFE_metis_style_adjwgt_array(); - - int* partition_map = new int[n]; - - PEID rank; MPI_Comm_rank( communicator, &rank); - - EdgeWeight prev_cut = 0; - NodeWeight prev_max_block_weight = 0; - - if( config.vcycle && rank == 0) { - forall_local_nodes(Q_bar, node) { - partition_map[node] = Q_bar.getSecondPartitionIndex(node); - } endfor - - prev_cut = dqm.local_edge_cut(Q_bar, partition_map, communicator); - prev_max_block_weight = dqm.local_max_block_weight(config, Q_bar, partition_map, communicator); - //std::cout << "prev cut " << prev_cut << std::endl; - //std::cout << "prev max block " << prev_max_block_weight << std::endl; - } - - if( config.vcycle ) { - MPI_Bcast(partition_map, n, MPI_INT, ROOT, communicator); - MPI_Bcast(&prev_cut, 1 , MPI_LONG, ROOT, communicator); - MPI_Bcast(&prev_max_block_weight, 1 , MPI_LONG, ROOT, communicator); - } - - double inbalance = config.inbalance/100.0; - int edgecut = 0; - double balance = 0; - bool graph_partitioned = config.vcycle; - - int mode = 0; - - switch( config.initial_partitioning_algorithm ) { - case KAFFPAESTRONG: - mode = STRONG; - break; - case KAFFPAEECO: - mode = ECO; - break; - case KAFFPAEFAST: - mode = FAST; - break; - case KAFFPAEULTRAFASTSNW: - mode = ULTRAFASTSOCIAL; - break; - case KAFFPAEFASTSNW: - mode = FASTSOCIAL; - break; - case KAFFPAEECOSNW: - mode = ECOSOCIAL; - break; - case KAFFPAESTRONGSNW: - mode = STRONGSOCIAL; - break; - default: - mode = FASTSOCIAL; - break; - } - - if(config.vcycle) { - forall_local_nodes(Q_bar, node) { - Q_bar.setNodeLabel(node, partition_map[node]); - } endfor - } - - timer t; - + mpi_tools mpitools; + parallel_graph_access Q_bar; + distributed_quality_metrics dqm; + static_cast(mpitools.preflight_serial_kernel(communicator, config, Q)); + mpitools.collect_parallel_graph_to_checked_serial_graph(communicator, config, + Q, Q_bar); + mpitools.distribute_local_graph( communicator, config, Q_bar); + + auto serial_input = std::optional{}; + try { + serial_input.emplace(checked_serial_input::from_graph(Q_bar, + config.vcycle)); + } catch (...) { + mpi::abort_on_exception(communicator, "serial input construction failure"); + } + int n = serial_input->node_count; + int nparts = config.k; // k-way partitioning. + + auto* xadj = serial_input->xadj.data(); + auto* adjncy = serial_input->adjncy.data(); + auto* vwgt = serial_input->node_weights.data(); + auto* adjwgt = serial_input->edge_weights.data(); + auto* partition_map = serial_input->partition.data(); + + [[maybe_unused]] int trivial_edgecut = 0; + [[maybe_unused]] double trivial_balance = 0.0; + if (kahip::serial_kernel::solve_trivial_single_block( + nparts, std::span{partition_map, static_cast(n)}, + trivial_edgecut, trivial_balance)) { + forall_local_nodes(Q_bar, node) { + Q_bar.setNodeLabel(node, 0); + } endfor + parallel_projection parallel_project_init; + parallel_project_init.initial_assignment(Q, Q_bar); + return; + } + + auto const mpi_communicator = mpi::communicator_view{communicator}; + PEID const rank = mpi_communicator.rank(); + + EdgeWeight prev_cut = 0; + NodeWeight prev_max_block_weight = 0; + + if( config.vcycle && rank == 0) { + forall_local_nodes(Q_bar, node) { + partition_map[node] = Q_bar.getSecondPartitionIndex(node); + } endfor + + prev_cut = dqm.local_edge_cut(Q_bar, partition_map, communicator); + prev_max_block_weight = dqm.local_max_block_weight(config, Q_bar, partition_map, communicator); + //std::cout << "prev cut " << prev_cut << std::endl; + //std::cout << "prev max block " << prev_max_block_weight << std::endl; + } + + if( config.vcycle ) { + mpi::broadcast_vcycle_state( + std::span{partition_map, static_cast(n)}, prev_cut, + prev_max_block_weight, ROOT, mpi_communicator); + } + + int edgecut = 0; + double balance = 0; + bool graph_partitioned = config.vcycle; + + int mode = 0; + + switch( config.initial_partitioning_algorithm ) { + case InitialPartitioningAlgorithm::KAFFPAESTRONG: + mode = STRONG; + break; + case InitialPartitioningAlgorithm::KAFFPAEECO: + mode = ECO; + break; + case InitialPartitioningAlgorithm::KAFFPAEFAST: + mode = FAST; + break; + case InitialPartitioningAlgorithm::KAFFPAEULTRAFASTSNW: + mode = ULTRAFASTSOCIAL; + break; + case InitialPartitioningAlgorithm::KAFFPAEFASTSNW: + mode = FASTSOCIAL; + break; + case InitialPartitioningAlgorithm::KAFFPAEECOSNW: + mode = ECOSOCIAL; + break; + case InitialPartitioningAlgorithm::KAFFPAESTRONGSNW: + mode = STRONGSOCIAL; + break; + default: + mode = FASTSOCIAL; + break; + } + + if(config.vcycle) { + forall_local_nodes(Q_bar, node) { + Q_bar.setNodeLabel(node, partition_map[node]); + } endfor +} + + timer t; + #ifdef NOOUTPUT - std::streambuf* backup = std::cout.rdbuf(); - std::ofstream ofs; - ofs.open("/dev/null"); - std::cout.rdbuf(ofs.rdbuf()); + std::streambuf* backup = std::cout.rdbuf(); + std::ofstream ofs; + ofs.open("/dev/null"); + std::cout.rdbuf(ofs.rdbuf()); #endif #ifdef DETERMINISTIC_PARHIP - kaffpaE(&n, - vwgt, - xadj, - adjwgt, - adjncy, - &nparts, - &inbalance, - false, // supress output - graph_partitioned, - 0, // time limit set to zero, so only the initial population is created - config.seed, - mode, - communicator, - &edgecut, - &balance, - partition_map); + auto const evolutionary_time_limit = 0; #else - kaffpaE(&n, - vwgt, - xadj, - adjwgt, - adjncy, - &nparts, - &inbalance, - false, // supress output - graph_partitioned, - config.evolutionary_time_limit, // time limit - config.seed, - mode, - communicator, - &edgecut, - &balance, - partition_map); + auto const evolutionary_time_limit = config.evolutionary_time_limit; #endif + kahip::modified::kaffpaE_with_upper_bound(&n, + vwgt, + xadj, + adjwgt, + adjncy, + &nparts, + false, // supress output + graph_partitioned, + evolutionary_time_limit, + config.seed, + mode, + communicator, + config.inbalance, + config.upper_bound_partition, + &edgecut, + &balance, + partition_map); + - #ifdef NOOUTPUT - ofs.close(); - std::cout.rdbuf(backup); + ofs.close(); + std::cout.rdbuf(backup); #endif - if( rank == (int)ROOT) { - PRINT(std::cout << "partitioner call took " << t.elapsed() << std::endl;); - } + if( rank == (int)ROOT) { + PRINT(std::cout << "partitioner call took " << t.elapsed() << std::endl;); + } #ifndef NOOUTPUT - if( rank == (int)ROOT) { - std::cout << "log>cut computed by IP algorithm " << edgecut << std::endl; - std::cout << "log>balance computed by IP algorithm "<< balance << std::endl; - } + if( rank == (int)ROOT) { + std::cout << "log>cut computed by IP algorithm " << edgecut << std::endl; + std::cout << "log>balance computed by IP algorithm "<< balance << std::endl; + } #endif - if( !config.vcycle ) { - forall_local_nodes(Q_bar, node) { - Q_bar.setNodeLabel(node, partition_map[node]); - } endfor - } else { - NodeWeight cur_max_block_weight = dqm.local_max_block_weight(config, Q_bar, partition_map, communicator); - - //balance and cut improved - bool accept = (cur_max_block_weight <= prev_max_block_weight || balance <= 1.03) && (EdgeWeight)edgecut <= prev_cut; - // or we previously have not been feasible and now are feasible - accept = accept || (prev_max_block_weight >= config.upper_bound_partition && cur_max_block_weight <= config.upper_bound_partition); - - if( accept ) { - if( rank == (int)ROOT) { - PRINT(std::cout << "log>update criterion reached, updating partition" << std::endl;) - - } - forall_local_nodes(Q_bar, node) { - Q_bar.setNodeLabel(node, partition_map[node]); - } endfor - } else { - if( rank == (int)ROOT) { - PRINT(std::cout << "update criterion not reached, not updating partition" << std::endl;) - } - } - } - - parallel_projection parallel_project_init; - parallel_project_init.initial_assignment( Q, Q_bar ); + if( !config.vcycle ) { + forall_local_nodes(Q_bar, node) { + Q_bar.setNodeLabel(node, partition_map[node]); + } endfor +} else { + NodeWeight cur_max_block_weight = dqm.local_max_block_weight(config, Q_bar, partition_map, communicator); + + //balance and cut improved + bool accept = (cur_max_block_weight <= prev_max_block_weight || balance <= 1.03) && (EdgeWeight)edgecut <= prev_cut; + // or we previously have not been feasible and now are feasible + accept = accept || (prev_max_block_weight >= config.upper_bound_partition && cur_max_block_weight <= config.upper_bound_partition); + + if( accept ) { + if( rank == (int)ROOT) { + PRINT(std::cout << "log>update criterion reached, updating partition" << std::endl;) + +} + forall_local_nodes(Q_bar, node) { + Q_bar.setNodeLabel(node, partition_map[node]); + } endfor +} else { + if( rank == (int)ROOT) { + PRINT(std::cout << "update criterion not reached, not updating partition" << std::endl;) +} +} +} + + parallel_projection parallel_project_init; + parallel_project_init.initial_assignment( Q, Q_bar ); #ifndef NOOUTPUT - edgecut = dqm.edge_cut(Q, communicator); - balance = dqm.balance(config, Q, communicator); - if( rank == (int)ROOT) { - std::cout << "log>cur edge cut " << edgecut << std::endl; - std::cout << "log>cur balance " << balance << std::endl; - } + edgecut = dqm.edge_cut(Q, communicator); + balance = dqm.balance(config, Q, communicator); + if( rank == (int)ROOT) { + std::cout << "log>cur edge cut " << edgecut << std::endl; + std::cout << "log>cur balance " << balance << std::endl; + } #endif - delete[] xadj; - delete[] adjncy; - delete[] vwgt; - delete[] adjwgt; - delete[] partition_map; } - +} diff --git a/parallel/parallel_src/lib/distributed_partitioning/initial_partitioning/distributed_evolutionary_partitioning.h b/parallel/parallel_src/lib/distributed_partitioning/initial_partitioning/distributed_evolutionary_partitioning.h index 17adcc6f..81f2416f 100644 --- a/parallel/parallel_src/lib/distributed_partitioning/initial_partitioning/distributed_evolutionary_partitioning.h +++ b/parallel/parallel_src/lib/distributed_partitioning/initial_partitioning/distributed_evolutionary_partitioning.h @@ -10,13 +10,13 @@ #include "partition_config.h" #include "data_structure/parallel_graph_access.h" - +namespace parhip { class distributed_evolutionary_partitioning { public: - distributed_evolutionary_partitioning(); - virtual ~distributed_evolutionary_partitioning(); + distributed_evolutionary_partitioning(); + virtual ~distributed_evolutionary_partitioning(); - void perform_partitioning( MPI_Comm communicator, PPartitionConfig & config, parallel_graph_access & G); + void perform_partitioning( MPI_Comm communicator, PPartitionConfig & config, parallel_graph_access & G); }; - +} #endif /* end of include guard: DISTRIBUTED_EVOLUTIONARY_PARTITIONING_OJ2RIKR7 */ diff --git a/parallel/parallel_src/lib/distributed_partitioning/initial_partitioning/initial_partitioning.cpp b/parallel/parallel_src/lib/distributed_partitioning/initial_partitioning/initial_partitioning.cpp index 3a7d2be6..5d37f2b5 100644 --- a/parallel/parallel_src/lib/distributed_partitioning/initial_partitioning/initial_partitioning.cpp +++ b/parallel/parallel_src/lib/distributed_partitioning/initial_partitioning/initial_partitioning.cpp @@ -8,7 +8,7 @@ #include "distributed_evolutionary_partitioning.h" #include "initial_partitioning.h" #include "random_initial_partitioning.h" - +namespace parhip { initial_partitioning_algorithm::initial_partitioning_algorithm() { } @@ -19,12 +19,14 @@ initial_partitioning_algorithm::~initial_partitioning_algorithm() { void initial_partitioning_algorithm::perform_partitioning( MPI_Comm communicator, PPartitionConfig & config, parallel_graph_access & Q) { - if( config.initial_partitioning_algorithm == RANDOMIP) { - random_initial_partitioning dist_rpart; - dist_rpart.perform_partitioning( communicator, config, Q ); - } else { - distributed_evolutionary_partitioning dist_epart; - dist_epart.perform_partitioning( communicator, config, Q); - } + if( config.initial_partitioning_algorithm == + InitialPartitioningAlgorithm::RANDOMIP) { + random_initial_partitioning dist_rpart; + dist_rpart.perform_partitioning( + mpi::communicator_view{communicator}, config, Q); + } else { + distributed_evolutionary_partitioning dist_epart; + dist_epart.perform_partitioning( communicator, config, Q); + } +} } - diff --git a/parallel/parallel_src/lib/distributed_partitioning/initial_partitioning/initial_partitioning.h b/parallel/parallel_src/lib/distributed_partitioning/initial_partitioning/initial_partitioning.h index e08f2c79..96133cc6 100644 --- a/parallel/parallel_src/lib/distributed_partitioning/initial_partitioning/initial_partitioning.h +++ b/parallel/parallel_src/lib/distributed_partitioning/initial_partitioning/initial_partitioning.h @@ -10,14 +10,14 @@ #include "partition_config.h" #include "data_structure/parallel_graph_access.h" - +namespace parhip { class initial_partitioning_algorithm { public: - initial_partitioning_algorithm(); - virtual ~initial_partitioning_algorithm(); + initial_partitioning_algorithm(); + virtual ~initial_partitioning_algorithm(); - void perform_partitioning( MPI_Comm communicator, PPartitionConfig & config, parallel_graph_access & G); + void perform_partitioning( MPI_Comm communicator, PPartitionConfig & config, parallel_graph_access & G); }; - +} #endif /* end of include guard: INITIAL_PARTITIONING_SFMCJN2U */ diff --git a/parallel/parallel_src/lib/distributed_partitioning/initial_partitioning/random_initial_partitioning.cpp b/parallel/parallel_src/lib/distributed_partitioning/initial_partitioning/random_initial_partitioning.cpp index bd0b51b9..130a852a 100644 --- a/parallel/parallel_src/lib/distributed_partitioning/initial_partitioning/random_initial_partitioning.cpp +++ b/parallel/parallel_src/lib/distributed_partitioning/initial_partitioning/random_initial_partitioning.cpp @@ -6,9 +6,74 @@ *****************************************************************************/ #include "random_initial_partitioning.h" + +#include +#include +#include + +#include "communication/mpi_collectives.h" +#include "communication/mpi_failure.h" #include "data_structure/parallel_graph_access.h" -#include "tools/random_functions.h" #include "tools/distributed_quality_metrics.h" +#include "tools/random_functions.h" +namespace parhip { +namespace { +void require_collectively(bool local_condition, + mpi::communicator_view communicator, + std::string_view diagnostic) noexcept { + if (!mpi::detail::collective_predicate(local_condition, communicator)) { + mpi::abort_on_programming_error(communicator.native_handle(), diagnostic); + } +} + +void require_compatible_graph_communicator( + mpi::communicator_view communicator, + parallel_graph_access& graph) noexcept { + auto const graph_communicator = graph.getCommunicator(); + require_collectively( + graph_communicator != MPI_COMM_NULL, communicator, + "random initial partitioning requires a live graph communicator"); + + auto relation = int{MPI_UNEQUAL}; + mpi::check_or_abort( + MPI_Comm_compare(communicator.native_handle(), graph_communicator, + &relation), + communicator.native_handle(), + "MPI_Comm_compare(random initial partitioning graph)"); + require_collectively( + relation == MPI_IDENT || relation == MPI_CONGRUENT, communicator, + "random initial partitioning graph communicator differs in process or " + "rank order"); +} + +void require_valid_block_count(PartitionID block_count, + mpi::communicator_view communicator) noexcept { + static_assert(sizeof(PartitionID) <= sizeof(std::uint64_t)); + auto const local = static_cast(block_count); + auto minimum = std::uint64_t{}; + auto maximum = std::uint64_t{}; + mpi::check_or_abort( + MPI_Allreduce(&local, &minimum, 1, MPI_UINT64_T, MPI_MIN, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(random initial partitioning k minimum)"); + mpi::check_or_abort( + MPI_Allreduce(&local, &maximum, 1, MPI_UINT64_T, MPI_MAX, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(random initial partitioning k maximum)"); + if (minimum != maximum) { + mpi::abort_on_programming_error( + communicator.native_handle(), + "random initial partitioning k differs across communicator"); + } + if (block_count == 0) { + mpi::abort_on_programming_error( + communicator.native_handle(), + "random initial partitioning requires k greater than zero"); + } +} +} // namespace random_initial_partitioning::random_initial_partitioning() { @@ -19,24 +84,45 @@ random_initial_partitioning::~random_initial_partitioning() { } -void random_initial_partitioning::perform_partitioning( MPI_Comm communicator, PPartitionConfig & config, parallel_graph_access & G) { +void random_initial_partitioning::perform_partitioning( + mpi::communicator_view communicator, + PPartitionConfig& config, + parallel_graph_access& G) { + mpi::require_live_intracommunicator( + communicator, + "random initial partitioning requires a live intracommunicator"); + auto const rank = communicator.rank(); + auto const size = communicator.size(); + require_collectively( + size > 0 && rank >= 0 && rank < size, communicator, + "random initial partitioning received an invalid communicator rank or " + "size"); + require_compatible_graph_communicator(communicator, G); + require_valid_block_count(config.k, communicator); - forall_local_nodes(G, node) { - G.setNodeLabel(node, random_functions::nextInt(0ULL, config.k-1)); - } endfor - - G.update_ghost_node_data_global(); // exchange the labels of ghost nodes + try { + forall_local_nodes(G, node) { + G.setNodeLabel( + node, + random_functions::nextInt( + NodeID{0}, config.k - PartitionID{1})); + } endfor - distributed_quality_metrics qm; - EdgeWeight edgecut = qm.edge_cut(G, communicator ); - double balance = qm.balance(config, G, communicator ); + G.update_ghost_node_data_global(); - PEID rank; - MPI_Comm_rank( communicator, &rank); - - if( rank == (int)ROOT) { - std::cout << "log>initial edge edge cut " << edgecut << std::endl; - std::cout << "log>initial imbalance " << balance << std::endl; - } + auto quality = distributed_quality_metrics{}; + auto const edge_cut = + quality.edge_cut(G, communicator.native_handle()); + auto const balance = + quality.balance(config, G, communicator.native_handle()); + if (rank == ROOT) { + std::cout << "log>initial edge edge cut " << edge_cut << '\n'; + std::cout << "log>initial imbalance " << balance << '\n'; + } + } catch (...) { + mpi::abort_on_exception(communicator.native_handle(), + "random initial partitioning failed"); + } +} } diff --git a/parallel/parallel_src/lib/distributed_partitioning/initial_partitioning/random_initial_partitioning.h b/parallel/parallel_src/lib/distributed_partitioning/initial_partitioning/random_initial_partitioning.h index 620613ad..e97b3aac 100644 --- a/parallel/parallel_src/lib/distributed_partitioning/initial_partitioning/random_initial_partitioning.h +++ b/parallel/parallel_src/lib/distributed_partitioning/initial_partitioning/random_initial_partitioning.h @@ -8,18 +8,21 @@ #ifndef RANDOM_INITIAL_PARTITIONING_FM8LJSI0 #define RANDOM_INITIAL_PARTITIONING_FM8LJSI0 -#include +#include "communication/mpi_handles.h" #include "partition_config.h" - +namespace parhip { class parallel_graph_access; class random_initial_partitioning { public: - random_initial_partitioning(); - virtual ~random_initial_partitioning(); - - void perform_partitioning( MPI_Comm communicator, PPartitionConfig & config, parallel_graph_access & G); -}; + random_initial_partitioning(); + virtual ~random_initial_partitioning(); + void perform_partitioning( + mpi::communicator_view communicator, + PPartitionConfig& config, + parallel_graph_access& G); +}; +} #endif /* end of include guard: RANDOM_INITIAL_PARTITIONING_FM8LJSI0 */ diff --git a/parallel/parallel_src/lib/distributed_partitioning/stop_rule.h b/parallel/parallel_src/lib/distributed_partitioning/stop_rule.h index b571645c..03548591 100644 --- a/parallel/parallel_src/lib/distributed_partitioning/stop_rule.h +++ b/parallel/parallel_src/lib/distributed_partitioning/stop_rule.h @@ -10,7 +10,7 @@ #include "data_structure/parallel_graph_access.h" #include "partition_config.h" - +namespace parhip { class stop_rule { public: stop_rule() {} ; @@ -22,6 +22,5 @@ class stop_rule { return false; } }; - - +} #endif /* end of include guard: STOP_RULE_23YOZ7GX */ diff --git a/parallel/parallel_src/lib/dspac/dspac.cpp b/parallel/parallel_src/lib/dspac/dspac.cpp index 0ecd1235..cd9070f9 100644 --- a/parallel/parallel_src/lib/dspac/dspac.cpp +++ b/parallel/parallel_src/lib/dspac/dspac.cpp @@ -8,252 +8,399 @@ #include "dspac.h" -dspac::dspac(parallel_graph_access &graph, MPI_Comm comm, EdgeWeight infinity) - : m_comm(comm), m_infinity(infinity), m_input_graph(graph) { +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_adapter.h" +#include "communication/mpi_fixed_reduction.h" +#include "communication/mpi_types.h" + +namespace parhip { +namespace { +[[nodiscard]] auto is_monotone(std::span values) noexcept + -> bool { + return std::ranges::adjacent_find(values, std::greater<>{}) == values.end(); } -void dspac::construct(parallel_graph_access &split_graph) { - assert(assert_adjacency_lists_sorted()); - MPI_Barrier(m_comm); - internal_construct(split_graph); - MPI_Barrier(m_comm); - assert(assert_sanity_checks(split_graph)); +void require_collectively(bool local_condition, + mpi::communicator_view communicator, + std::string_view diagnostic) noexcept { + if (!mpi::detail::collective_predicate(local_condition, communicator)) { + mpi::abort_on_programming_error(communicator.native_handle(), diagnostic); + } } -void dspac::internal_construct(parallel_graph_access &split_graph) { - int size, rank; - MPI_Comm_size(m_comm, &size); - MPI_Comm_rank(m_comm, &rank); - const NodeID n = m_input_graph.number_of_global_nodes(); -#ifndef NDEBUG - const NodeID m = m_input_graph.number_of_global_edges(); -#endif +[[nodiscard]] auto arrays_agree_collectively( + std::span values, + mpi::communicator_view communicator) -> bool { + auto minimum = std::vector(values.size()); + auto maximum = std::vector(values.size()); + mpi::all_reduce_bounded( + values, std::span{minimum}, mpi::reduction_kind::minimum, + communicator, "MPI_Allreduce(DSPAC range minimum)"); + mpi::all_reduce_bounded( + values, std::span{maximum}, mpi::reduction_kind::maximum, + communicator, "MPI_Allreduce(DSPAC range maximum)"); + return minimum == maximum; +} + +[[nodiscard]] auto checked_split_edge_count(EdgeID directed_edges, + NodeID low_degree_vertices, + EdgeID& result) noexcept -> bool { + constexpr auto maximum = std::numeric_limits::max(); + if (low_degree_vertices > directed_edges) { + return false; + } + // 3m - 2c == m + 2(m - c), and every counted degree-one/two vertex + // contributes at least one directed edge. This form admits every + // representable result without overflowing an intermediate expression. + auto const additional_edges = directed_edges - low_degree_vertices; + if (additional_edges > (maximum - directed_edges) / 2) { + return false; + } + result = directed_edges + EdgeID{2} * additional_edges; + return true; +} +} // namespace + +dspac::dspac(parallel_graph_access& graph, + MPI_Comm comm, + EdgeWeight infinity, + mpi::collective_options collective_options) + : m_comm(comm), + m_infinity(infinity), + m_input_graph(graph), + m_collective_options(collective_options) {} + +void dspac::construct(parallel_graph_access& split_graph) { + auto operation_communicator = + mpi::communicator{mpi::communicator_view{m_comm}}; + auto const communicator = operation_communicator.view(); + mpi::check_or_abort(MPI_Barrier(communicator.native_handle()), + communicator.native_handle(), + "MPI_Barrier(before DSPAC construction)"); + internal_construct(split_graph, communicator); + mpi::check_or_abort(MPI_Barrier(communicator.native_handle()), + communicator.native_handle(), + "MPI_Barrier(after DSPAC construction)"); + assert(assert_sanity_checks(split_graph)); +} + +void dspac::internal_construct(parallel_graph_access& split_graph, + mpi::communicator_view communicator) { + auto const size = communicator.size(); + auto const rank = communicator.rank(); + + try { + auto const n = m_input_graph.number_of_global_nodes(); + auto const global_input_edges = m_input_graph.number_of_global_edges(); + auto const local_input_nodes = m_input_graph.number_of_local_nodes(); + auto const local_input_edges = m_input_graph.number_of_local_edges(); + auto const range_count = static_cast(size) + 1; + auto const rank_index = static_cast(rank); timer construction_timer; - // we construct the split nodes from..(to - 1) on this node auto edge_range_array = m_input_graph.get_edge_range_array(); - assert(assert_edge_range_array_ok(edge_range_array)); - const std::size_t from = edge_range_array[rank]; // inclusive - const std::size_t to = edge_range_array[rank + 1]; // exclusive - assert(to <= m_input_graph.number_of_global_edges()); - - // we need the number of vertices of degree 1 or 2 to calculate the dimension of the split graph - NodeID local_number_of_deg_1_or_2_vertices = 0; - for (NodeID v = 0; v < m_input_graph.number_of_local_nodes(); ++v) { - EdgeID deg = m_input_graph.getNodeDegree(v); - if (deg == 1 || deg == 2) { - ++local_number_of_deg_1_or_2_vertices; - } + auto node_range_array = m_input_graph.get_range_array(); + require_collectively( + edge_range_array.size() == range_count && + node_range_array.size() == range_count, + communicator, "DSPAC range arrays must contain one entry per rank boundary"); + + // The legacy loader may leave only this terminal value stale. + node_range_array.back() = n; + auto const ranges_are_locally_valid = + edge_range_array.front() == 0 && + edge_range_array.back() == global_input_edges && + node_range_array.front() == 0 && node_range_array.back() == n && + is_monotone(edge_range_array) && is_monotone(node_range_array) && + edge_range_array[rank_index + 1] - edge_range_array[rank_index] == + local_input_edges && + node_range_array[rank_index + 1] - node_range_array[rank_index] == + local_input_nodes && + std::ranges::all_of(node_range_array, [](NodeID value) { + return std::in_range(value); + }); + require_collectively(ranges_are_locally_valid, communicator, + "DSPAC graph ranges are invalid"); + require_collectively( + arrays_agree_collectively(edge_range_array, communicator) && + arrays_agree_collectively(node_range_array, communicator), + communicator, "DSPAC graph ranges differ across ranks"); + + auto const from = edge_range_array[rank_index]; // inclusive + auto const to = edge_range_array[rank_index + 1]; // exclusive + + auto local_number_of_deg_1_or_2_vertices = NodeID{}; + for (NodeID vertex = 0; vertex < local_input_nodes; ++vertex) { + auto const degree = m_input_graph.getNodeDegree(vertex); + if (degree == 1 || degree == 2) { + ++local_number_of_deg_1_or_2_vertices; + } } - - NodeID global_number_of_deg_1_or_2_vertices = 0; - MPI_Allreduce(&local_number_of_deg_1_or_2_vertices, &global_number_of_deg_1_or_2_vertices, 1, - MPI_UNSIGNED_LONG_LONG, MPI_SUM, m_comm); + auto const global_number_of_deg_1_or_2_vertices = mpi::all_reduce_sum( + local_number_of_deg_1_or_2_vertices, communicator, + "MPI_Allreduce(DSPAC degree-one-or-two count)"); if (rank == 0) { - std::cout << "[dspac::internal_construct()] Up to MPI_Allreduce() took " - << construction_timer.elapsed() << std::endl; - construction_timer.restart(); + std::cout << "[dspac::internal_construct()] Up to MPI_Allreduce() took " + << construction_timer.elapsed() << std::endl; + construction_timer.restart(); } - // calculate split graph dimensions - const NodeID local_number_of_split_nodes = m_input_graph.number_of_local_edges(); - const EdgeID global_number_of_split_nodes = m_input_graph.number_of_global_edges(); - - assert(3 * m_input_graph.number_of_local_edges() >= 2 * local_number_of_deg_1_or_2_vertices); - const EdgeID local_number_of_split_edges = 3 * m_input_graph.number_of_local_edges() - - 2 * local_number_of_deg_1_or_2_vertices; - - assert(3 * m_input_graph.number_of_global_edges() >= 2 * global_number_of_deg_1_or_2_vertices); - const NodeID global_number_of_split_edges = 3 * m_input_graph.number_of_global_edges() - - 2 * global_number_of_deg_1_or_2_vertices; - - // this array stores the distribution of nodes across PEs, namely PE i stores nodes - // node_range_array[i]..node_range_array[i + 1]-1 - // the default loader sets a wrong value for node_range_array[size] though, so we need to fix that for our purposes - // here - auto node_range_array = m_input_graph.get_range_array(); - node_range_array[size] = m_input_graph.number_of_global_nodes(); - assert(assert_node_range_array_ok(node_range_array)); - - std::vector> first_split_node_on(size); - - // first, reserve memory for adjacent PEs - for (PEID pe = 0; pe < size; ++pe) { - if (m_input_graph.is_adjacent_PE(pe) || pe == rank) { - first_split_node_on[pe].resize(m_input_graph.number_of_local_nodes()); + auto const local_number_of_split_nodes = local_input_edges; + auto const global_number_of_split_nodes = global_input_edges; + auto local_number_of_split_edges = EdgeID{}; + auto global_number_of_split_edges = EdgeID{}; + require_collectively( + checked_split_edge_count(local_input_edges, + local_number_of_deg_1_or_2_vertices, + local_number_of_split_edges) && + checked_split_edge_count(global_input_edges, + global_number_of_deg_1_or_2_vertices, + global_number_of_split_edges), + communicator, "DSPAC split-graph dimension arithmetic is invalid"); + + auto outgoing_ranks = std::vector{}; + auto adjacency_is_valid = true; + for (NodeID vertex = 0; vertex < local_input_nodes; ++vertex) { + auto previous_global_target = NodeID{}; + auto has_previous_target = false; + for (auto edge = m_input_graph.get_first_edge(vertex), + end = m_input_graph.get_first_invalid_edge(vertex); + edge < end; ++edge) { + auto const target = m_input_graph.getEdgeTarget(edge); + auto const global_target = m_input_graph.getGlobalID(target); + auto const owner = m_input_graph.is_local_node(target) + ? rank + : m_input_graph.getTargetPE(target); + adjacency_is_valid = + adjacency_is_valid && global_target < n && owner >= 0 && + owner < size && + (!has_previous_target || previous_global_target <= global_target); + if (owner != rank && owner >= 0 && owner < size) { + outgoing_ranks.push_back(owner); } + previous_global_target = global_target; + has_previous_target = true; + } + } + require_collectively(adjacency_is_valid, communicator, + "DSPAC adjacency must be sorted and have valid owners"); + std::ranges::sort(outgoing_ranks); + auto const unique_ranks = std::ranges::unique(outgoing_ranks); + outgoing_ranks.erase(unique_ranks.begin(), unique_ranks.end()); + + auto const local_node_count = static_cast(local_input_nodes); + auto first_split_node_on = std::vector>( + static_cast(size)); + first_split_node_on[rank_index].resize(local_node_count); + for (auto const destination : outgoing_ranks) { + first_split_node_on[static_cast(destination)].resize( + local_node_count); } - // then fill the reserved memory - for (NodeID v = 0; v < m_input_graph.number_of_local_nodes(); ++v) { - PEID current_pe = -1; - for (EdgeID e = m_input_graph.get_first_edge(v); e < m_input_graph.get_first_invalid_edge(v); ++e) { - NodeID u = m_input_graph.getEdgeTarget(e); - PEID pe = m_input_graph.is_local_node(u) ? static_cast(rank) : m_input_graph.getTargetPE(u); - - if (pe != current_pe) { - assert(first_split_node_on[pe].size() == m_input_graph.number_of_local_nodes()); - first_split_node_on[pe][v] = from + e; - current_pe = pe; - } + for (NodeID vertex = 0; vertex < local_input_nodes; ++vertex) { + auto current_owner = PEID{-1}; + for (auto edge = m_input_graph.get_first_edge(vertex), + end = m_input_graph.get_first_invalid_edge(vertex); + edge < end; ++edge) { + auto const target = m_input_graph.getEdgeTarget(edge); + auto const owner = m_input_graph.is_local_node(target) + ? rank + : m_input_graph.getTargetPE(target); + if (owner != current_owner) { + first_split_node_on[static_cast(owner)] + [static_cast(vertex)] = from + edge; + current_owner = owner; } + } } if (rank == 0) { - std::cout << "[dspac::internal_construct()] Preparation of first_split_node_on[] took " - << construction_timer.elapsed() << std::endl; - construction_timer.restart(); + std::cout << "[dspac::internal_construct()] Preparation of " + "first_split_node_on[] took " + << construction_timer.elapsed() << std::endl; + construction_timer.restart(); } - // once created, this array has the following semantic: say we have an edge vu in the original graph where - // v is on our PE and u is on any PE - // when we create the split graph, when need to connect one split vertex of v and one of u to represent the vu edge - // in the split graph - // so when we connect the split vertices of v, we use first_split_node[globalId(u)] as the split node id of u and - // then increment it by one, so that when need a split vertex of u again on this PE, we use the next one and so on - std::vector first_split_node(n); // contains global node ids - - // receive the messages from adjacent PEs and place them at the right position in first_split_node: the messages - // from PE i should be placed starting at node_range_array[i] - std::vector requests; - - // send the messages to adjacent PEs - for (PEID pe = 0; pe < size; ++pe) { - if (m_input_graph.is_adjacent_PE(pe)) { - assert(rank != pe); - - NodeID *buf = &first_split_node_on[pe][0]; - const std::size_t count = first_split_node_on[pe].size(); - - assert(count == node_range_array[rank + 1] - node_range_array[rank]); - assert(count < std::numeric_limits::max()); - - MPI_Request *request = new MPI_Request; - MPI_Isend(buf, static_cast(count), MPI_UNSIGNED_LONG_LONG, pe, 0, m_comm, request); - requests.push_back(request); - } - } - - // copy own data from first_split_node_on to first_split_node - assert(first_split_node_on[rank].size() == m_input_graph.number_of_local_nodes()); - assert(first_split_node_on[rank].size() == node_range_array[rank + 1] - node_range_array[rank]); - assert(first_split_node.data() + node_range_array[rank] + m_input_graph.number_of_local_nodes() - <= (&first_split_node[n - 1]) + 1); - std::copy(first_split_node_on[rank].begin(), first_split_node_on[rank].end(), - first_split_node.begin() + node_range_array[rank]); - - // receive messages from adjacent neighbors - for (PEID pe = 0; pe < size; ++pe) { - if (m_input_graph.is_adjacent_PE(pe)) { - assert(rank != pe); - - NodeID *buf = &first_split_node[node_range_array[pe]]; - const NodeID count = node_range_array[pe + 1] - node_range_array[pe]; - - assert(node_range_array[pe] + count <= first_split_node.size()); - assert(count < std::numeric_limits::max()); - - MPI_Recv(buf, static_cast(count), MPI_UNSIGNED_LONG_LONG, pe, 0, m_comm, MPI_STATUS_IGNORE); + auto first_split_node = + std::vector(static_cast(n)); + { + auto topology = mpi::distributed_graph{communicator, outgoing_ranks}; + auto outgoing = std::vector>{}; + outgoing.reserve(topology.destinations().size()); + for (auto const destination : topology.destinations()) { + outgoing.push_back( + first_split_node_on[static_cast(destination)]); + } + auto received = mpi::neighbor_all_to_all_v( + mpi::segmented_buffer::from_segments(outgoing), topology, + m_collective_options); + + auto received_shape_is_valid = + received.segment_count() == topology.sources().size(); + if (received_shape_is_valid) { + for (std::size_t index = 0; index < topology.sources().size(); ++index) { + auto const source = topology.sources()[index]; + auto const source_is_valid = source >= 0 && source < size; + received_shape_is_valid = + received_shape_is_valid && source_is_valid; + if (!source_is_valid) { + continue; + } + auto const source_index = static_cast(source); + auto const expected = node_range_array[source_index + 1] - + node_range_array[source_index]; + received_shape_is_valid = + received_shape_is_valid && + std::in_range(expected) && + received.segment(index).size() == + static_cast(expected); } - } - - // wait for own messages to be received - for (MPI_Request *request : requests) { - MPI_Wait(request, MPI_STATUS_IGNORE); - delete request; + } + require_collectively(received_shape_is_valid, topology.view(), + "DSPAC first-split source segment extent mismatch"); + + auto const own_offset = + static_cast(node_range_array[rank_index]); + std::ranges::copy(first_split_node_on[rank_index], + first_split_node.begin() + own_offset); + for (std::size_t index = 0; index < topology.sources().size(); ++index) { + auto const source_index = + static_cast(topology.sources()[index]); + auto const offset = + static_cast(node_range_array[source_index]); + std::ranges::copy(received.segment(index), + first_split_node.begin() + offset); + } } if (rank == 0) { - std::cout << "[dspac::internal_construct()] first_split_node[] communication took " - << construction_timer.elapsed() << std::endl; - construction_timer.restart(); + std::cout << "[dspac::internal_construct()] first_split_node[] " + "communication took " + << construction_timer.elapsed() << std::endl; + construction_timer.restart(); } - // we no longer need first_split_node_on from now on since it's copied to first_split_node on each PE first_split_node_on.clear(); - // now we construct the split graph - split_graph.start_construction(local_number_of_split_nodes, local_number_of_split_edges, - global_number_of_split_nodes, global_number_of_split_edges); + split_graph.start_construction( + local_number_of_split_nodes, local_number_of_split_edges, + global_number_of_split_nodes, global_number_of_split_edges); split_graph.set_range_array(edge_range_array); - split_graph.set_range(from, to - 1); - - NodeID nodes_created = 0; - EdgeID edges_created = 0; - - for (NodeID v = 0; v < m_input_graph.number_of_local_nodes(); ++v) { - EdgeID deg = m_input_graph.getNodeDegree(v); - if (deg == 0) { // explicitly skip isolated nodes - continue; + split_graph.set_range(from, from == to ? from : to - EdgeID{1}); + + auto nodes_created = NodeID{}; + auto edges_created = EdgeID{}; + for (NodeID vertex = 0; vertex < local_input_nodes; ++vertex) { + auto const degree = m_input_graph.getNodeDegree(vertex); + if (degree == 0) { + continue; + } + + for (auto edge = m_input_graph.get_first_edge(vertex), + end = m_input_graph.get_first_invalid_edge(vertex); + edge < end; ++edge) { + auto const target = m_input_graph.getEdgeTarget(edge); + auto const global_target = m_input_graph.getGlobalID(target); + if (!std::in_range(global_target) || + static_cast(global_target) >= + first_split_node.size()) { + mpi::abort_on_programming_error( + communicator.native_handle(), + "DSPAC dominant-edge target is outside the node domain"); } - for (EdgeID e = m_input_graph.get_first_edge(v); e < m_input_graph.get_first_invalid_edge(v); ++e) { - NodeID u = m_input_graph.getEdgeTarget(e); - NodeID global_u = m_input_graph.getGlobalID(u); - - // create the split node - ++nodes_created; - NodeID split_node = split_graph.new_node(); - assert(split_node == e); - - split_graph.setNodeWeight(split_node, 1); - split_graph.setNodeLabel(split_node, from + split_node); - split_graph.setSecondPartitionIndex(split_node, 0); - - // create dominant edge - ++edges_created; - assert(global_u < first_split_node.size()); - NodeID target_node = first_split_node[global_u]; - EdgeID dominant_edge = split_graph.new_edge(split_node, target_node); - ++first_split_node[global_u]; - split_graph.setEdgeWeight(dominant_edge, m_infinity); - - // create auxiliary edges - bool first = (e == m_input_graph.get_first_edge(v)); - bool last = (e + 1 == m_input_graph.get_first_invalid_edge(v)); - - if (deg == 2) { - // degree 2: we create a path with a single edge in the split graph - ++edges_created; - int target_offset = first ? 1 : -1; - assert(0 <= split_node + target_offset && split_node + target_offset < local_number_of_split_nodes); - EdgeID auxiliary_edge = split_graph.new_edge(split_node, from + split_node + target_offset); - split_graph.setEdgeWeight(auxiliary_edge, 1); - } else if (deg > 2) { - // degree > 2: we create a cycle with all split nodes, thus we need a edge to the previous and one to - // the next node in the cycle - ++edges_created; - int next_offset = last ? -(static_cast(deg) - 1) : 1; - NodeID global_next = from + split_node + next_offset; - assert(from == split_graph.get_from_range()); - assert(split_graph.get_from_range() <= global_next && global_next <= split_graph.get_to_range()); - - EdgeID next_auxiliary_edge = split_graph.new_edge(split_node, global_next); - split_graph.setEdgeWeight(next_auxiliary_edge, 1); - - ++edges_created; - int prev_offset = first ? static_cast(deg) - 1 : -1; - NodeID global_prev = from + split_node + prev_offset; - assert(split_graph.get_from_range() <= global_prev && global_prev <= split_graph.get_to_range()); - EdgeID prev_auxiliary_edge = split_graph.new_edge(split_node, global_prev); - split_graph.setEdgeWeight(prev_auxiliary_edge, 1); - } else { - assert(deg == 1); - // nothing to do for leaves - } + ++nodes_created; + auto const split_node = split_graph.new_node(); + if (split_node != edge) { + mpi::abort_on_programming_error( + communicator.native_handle(), + "DSPAC split-node construction order diverged"); + } + split_graph.setNodeWeight(split_node, 1); + split_graph.setNodeLabel(split_node, from + split_node); + split_graph.setSecondPartitionIndex(split_node, 0); + + auto& first_target = + first_split_node[static_cast(global_target)]; + if (first_target >= global_number_of_split_nodes) { + mpi::abort_on_programming_error( + communicator.native_handle(), + "DSPAC reciprocal split-node mapping is invalid"); + } + ++edges_created; + auto const dominant_edge = + split_graph.new_edge(split_node, first_target); + ++first_target; + split_graph.setEdgeWeight(dominant_edge, m_infinity); + + auto const first = edge == m_input_graph.get_first_edge(vertex); + auto const last = edge + 1 == end; + if (degree == 2) { + auto const auxiliary_local = + first ? split_node + NodeID{1} : split_node - NodeID{1}; + if (auxiliary_local >= local_number_of_split_nodes) { + mpi::abort_on_programming_error( + communicator.native_handle(), + "DSPAC degree-two auxiliary edge is invalid"); + } + ++edges_created; + auto const auxiliary_edge = + split_graph.new_edge(split_node, from + auxiliary_local); + split_graph.setEdgeWeight(auxiliary_edge, 1); + } else if (degree > 2) { + auto const span = degree - EdgeID{1}; + auto const next_local = + last ? split_node - span : split_node + NodeID{1}; + auto const previous_local = + first ? split_node + span : split_node - NodeID{1}; + if (next_local >= local_number_of_split_nodes || + previous_local >= local_number_of_split_nodes) { + mpi::abort_on_programming_error( + communicator.native_handle(), + "DSPAC cycle auxiliary edge is invalid"); + } + ++edges_created; + auto const next_edge = + split_graph.new_edge(split_node, from + next_local); + split_graph.setEdgeWeight(next_edge, 1); + ++edges_created; + auto const previous_edge = + split_graph.new_edge(split_node, from + previous_local); + split_graph.setEdgeWeight(previous_edge, 1); + } else if (degree != 1) { + mpi::abort_on_programming_error( + communicator.native_handle(), + "DSPAC encountered an invalid nonzero degree"); } + } } if (rank == 0) { - std::cout << "[dspac::internal_construct()] Local construction took " - << construction_timer.elapsed() << std::endl; - construction_timer.restart(); + std::cout << "[dspac::internal_construct()] Local construction took " + << construction_timer.elapsed() << std::endl; + construction_timer.restart(); + } + if (nodes_created != local_number_of_split_nodes || + edges_created != local_number_of_split_edges) { + mpi::abort_on_programming_error( + communicator.native_handle(), + "DSPAC local split-graph dimensions diverged"); } - - assert(nodes_created == local_number_of_split_nodes); - assert(edges_created == local_number_of_split_edges); split_graph.finish_construction(); + } catch (...) { + mpi::abort_on_exception(communicator.native_handle(), + "DSPAC split-graph construction failed"); + } } /** @@ -262,196 +409,262 @@ void dspac::internal_construct(parallel_graph_access &split_graph) { */ bool dspac::assert_sanity_checks(parallel_graph_access &split_graph) { #ifndef NDEBUG - assert(split_graph.number_of_local_nodes() == m_input_graph.number_of_local_edges()); - for (NodeID v = 0; v < split_graph.number_of_local_nodes(); ++v) { - // isolated vertices should be removed for now - assert(0 < split_graph.getNodeDegree(v) && split_graph.getNodeDegree(v) <= 3); - - // make sure that the edge weights are correct, i.e. auxiliary edges have edge weight 1 and - // dominant edges have edge weight m_infinity - EdgeID firstEdge = split_graph.get_first_edge(v); - switch (split_graph.getNodeDegree(v)) { - case 3: // fall through intended - assert(split_graph.getEdgeWeight(firstEdge + 2) == 1); - - case 2: - assert(split_graph.getEdgeWeight(firstEdge + 1) == 1); - - case 1: - assert(split_graph.getEdgeWeight(firstEdge) == m_infinity); - break; - - default: - assert(false); - } + assert(split_graph.number_of_local_nodes() == m_input_graph.number_of_local_edges()); + for (NodeID v = 0; v < split_graph.number_of_local_nodes(); ++v) { + // isolated vertices should be removed for now + assert(0 < split_graph.getNodeDegree(v) && split_graph.getNodeDegree(v) <= 3); + + // make sure that the edge weights are correct, i.e. auxiliary edges have edge weight 1 and + // dominant edges have edge weight m_infinity + EdgeID firstEdge = split_graph.get_first_edge(v); + switch (split_graph.getNodeDegree(v)) { + case 3: // fall through intended + assert(split_graph.getEdgeWeight(firstEdge + 2) == 1); + + case 2: + assert(split_graph.getEdgeWeight(firstEdge + 1) == 1); + + case 1: + assert(split_graph.getEdgeWeight(firstEdge) == m_infinity); + break; + + default: + assert(false); } + } - // this part checks that the auxiliary edges are connected to the right nodes - for (NodeID v = 0; v < m_input_graph.number_of_local_nodes(); ++v) { - EdgeID deg = m_input_graph.getNodeDegree(v); - if (deg == 0) { // explicitly skip isolated nodes - continue; - } + // this part checks that the auxiliary edges are connected to the right nodes + for (NodeID v = 0; v < m_input_graph.number_of_local_nodes(); ++v) { + EdgeID deg = m_input_graph.getNodeDegree(v); + if (deg == 0) { // explicitly skip isolated nodes + continue; + } - for (EdgeID e = m_input_graph.get_first_edge(v); e < m_input_graph.get_first_invalid_edge(v); ++e) { - bool first = (e == m_input_graph.get_first_edge(v)); - bool last = (e + 1 == m_input_graph.get_first_invalid_edge(v)); - - if (deg == 1) { - if (split_graph.get_first_edge(e) + 1 < split_graph.number_of_local_edges()) { - // degree 1 node --> no auxiliary edges --> next edge must be a dominant edge of another node - assert(split_graph.getEdgeWeight(split_graph.get_first_edge(e) + 1) == m_infinity); - } - } else if (deg == 2) { - if (first) { - // first split node --> auxiliary edge must target the second split node - assert(split_graph.getEdgeTarget(split_graph.get_first_edge(e) + 1) == e + 1); - } else if (last) { - // second split node --> auxiliary edge must target the first split node - assert(split_graph.getEdgeTarget(split_graph.get_first_edge(e) + 1) == e - 1); - } else { - assert(false); - } - - // a dominant edge must follow a single auxiliary edge - if (split_graph.get_first_edge(e) + 2 < split_graph.number_of_local_edges()) { - assert(split_graph.getEdgeWeight(split_graph.get_first_edge(e) + 2) == m_infinity); - } - } else if (deg > 2) { - if (first) { - assert(split_graph.getEdgeTarget(split_graph.get_first_edge(e) + 1) == e + 1); - assert(split_graph.getEdgeTarget(split_graph.get_first_edge(e) + 2) == e + (deg - 1)); - } else if (last) { - assert(split_graph.getEdgeTarget(split_graph.get_first_edge(e) + 1) == e - (deg - 1)); - assert(split_graph.getEdgeTarget(split_graph.get_first_edge(e) + 2) == e - 1); - } else { - assert(split_graph.getEdgeTarget(split_graph.get_first_edge(e) + 1) == e + 1); - assert(split_graph.getEdgeTarget(split_graph.get_first_edge(e) + 2) == e - 1); - } - - // a dominant edge must follow after two auxiliary edges - if (split_graph.get_first_edge(e) + 3 < split_graph.number_of_local_edges()) { - assert(split_graph.getEdgeWeight(split_graph.get_first_edge(e) + 3) == m_infinity); - } - } else { - assert(false); - } + for (EdgeID e = m_input_graph.get_first_edge(v); e < m_input_graph.get_first_invalid_edge(v); ++e) { + bool first = (e == m_input_graph.get_first_edge(v)); + bool last = (e + 1 == m_input_graph.get_first_invalid_edge(v)); + + if (deg == 1) { + if (split_graph.get_first_edge(e) + 1 < split_graph.number_of_local_edges()) { + // degree 1 node --> no auxiliary edges --> next edge must be a dominant edge of another node + assert(split_graph.getEdgeWeight(split_graph.get_first_edge(e) + 1) == m_infinity); + } + } else if (deg == 2) { + if (first) { + // first split node --> auxiliary edge must target the second split node + assert(split_graph.getEdgeTarget(split_graph.get_first_edge(e) + 1) == e + 1); + } else if (last) { + // second split node --> auxiliary edge must target the first split node + assert(split_graph.getEdgeTarget(split_graph.get_first_edge(e) + 1) == e - 1); + } else { + assert(false); } - } -#endif - return true; -} -/** - * assert()'s that the adjacency lists of the input graph are sorted. - * @return Pointless bool so that the method call can be used as expression. - */ -bool dspac::assert_adjacency_lists_sorted() { -#ifndef NDEBUG - for (NodeID v = 0; v < m_input_graph.number_of_local_nodes(); ++v) { - if (m_input_graph.getNodeDegree(v) == 0) { - continue; + // a dominant edge must follow a single auxiliary edge + if (split_graph.get_first_edge(e) + 2 < split_graph.number_of_local_edges()) { + assert(split_graph.getEdgeWeight(split_graph.get_first_edge(e) + 2) == m_infinity); + } + } else if (deg > 2) { + if (first) { + assert(split_graph.getEdgeTarget(split_graph.get_first_edge(e) + 1) == e + 1); + assert(split_graph.getEdgeTarget(split_graph.get_first_edge(e) + 2) == e + (deg - 1)); + } else if (last) { + assert(split_graph.getEdgeTarget(split_graph.get_first_edge(e) + 1) == e - (deg - 1)); + assert(split_graph.getEdgeTarget(split_graph.get_first_edge(e) + 2) == e - 1); + } else { + assert(split_graph.getEdgeTarget(split_graph.get_first_edge(e) + 1) == e + 1); + assert(split_graph.getEdgeTarget(split_graph.get_first_edge(e) + 2) == e - 1); } - NodeID local_first_neighbor = m_input_graph.getEdgeTarget(m_input_graph.get_first_edge(v)); - auto global_first_neighbor = static_cast(m_input_graph.getGlobalID(local_first_neighbor)); - NodeID cur = global_first_neighbor; - for (EdgeID e = m_input_graph.get_first_edge(v); e < m_input_graph.get_first_invalid_edge(v); ++e) { - NodeID u = m_input_graph.getEdgeTarget(e); - assert(cur <= m_input_graph.getGlobalID(u)); + // a dominant edge must follow after two auxiliary edges + if (split_graph.get_first_edge(e) + 3 < split_graph.number_of_local_edges()) { + assert(split_graph.getEdgeWeight(split_graph.get_first_edge(e) + 3) == m_infinity); } + } else { + assert(false); + } } + } #endif - return true; + return true; } -bool dspac::assert_edge_range_array_ok(const std::vector &edge_range_array) { - int size, rank; - MPI_Comm_size(m_comm, &size); - MPI_Comm_rank(m_comm, &rank); - assert(edge_range_array.size() == size + 1); - assert(edge_range_array[0] == 0); - assert(edge_range_array[size] == m_input_graph.number_of_global_edges()); - assert(m_input_graph.number_of_local_edges() == edge_range_array[rank + 1] - edge_range_array[rank]); - for (std::size_t pe = 0; pe < (size_t)size; ++pe) - assert(edge_range_array[pe] <= edge_range_array[pe + 1]); - return true; -} - -bool dspac::assert_node_range_array_ok(const std::vector &node_range_array) { - int size, rank; - MPI_Comm_size(m_comm, &size); - MPI_Comm_rank(m_comm, &rank); - assert(node_range_array.size() == size + 1); - assert(node_range_array[0] == 0); - assert(node_range_array[size] == m_input_graph.number_of_global_nodes()); - assert(m_input_graph.number_of_local_nodes() == node_range_array[rank + 1] - node_range_array[rank]); - return true; -} - -std::vector dspac::project_partition(parallel_graph_access &split_graph, const std::vector &permutation) { - std::vector edge_partition(m_input_graph.number_of_local_edges()); +std::vector dspac::project_partition( + parallel_graph_access& split_graph, + std::vector const& permutation) { + auto operation_communicator = + mpi::communicator{mpi::communicator_view{m_comm}}; + auto const communicator = operation_communicator.view(); + try { + auto const local_edges = m_input_graph.number_of_local_edges(); + auto local_permutation_is_valid = + std::in_range(local_edges) && + split_graph.number_of_local_nodes() == local_edges; + auto local_edge_count = std::size_t{}; + if (local_permutation_is_valid) { + local_edge_count = static_cast(local_edges); + local_permutation_is_valid = permutation.size() == local_edge_count; + } - for (NodeID v = 0; v < m_input_graph.number_of_local_nodes(); ++v) { - for (EdgeID e = m_input_graph.get_first_edge(v); e < m_input_graph.get_first_invalid_edge(v); ++e) { - edge_partition[permutation[e]] = split_graph.getNodeLabel(e); + auto seen = std::vector{}; + if (local_permutation_is_valid) { + seen.resize(local_edge_count); + for (auto const target : permutation) { + if (target >= local_edges || + seen[static_cast(target)]) { + local_permutation_is_valid = false; + break; } + seen[static_cast(target)] = true; + } + } + require_collectively( + local_permutation_is_valid, communicator, + "DSPAC projection permutation must be a bijection over local edges"); + + auto edge_partition = std::vector(local_edge_count); + for (NodeID vertex = 0; + vertex < m_input_graph.number_of_local_nodes(); ++vertex) { + for (auto edge = m_input_graph.get_first_edge(vertex), + end = m_input_graph.get_first_invalid_edge(vertex); + edge < end; ++edge) { + edge_partition[static_cast( + permutation[static_cast(edge)])] = + split_graph.getNodeLabel(edge); + } } - MPI_Barrier(m_comm); + mpi::check_or_abort(MPI_Barrier(communicator.native_handle()), + communicator.native_handle(), + "MPI_Barrier(after DSPAC projection)"); return edge_partition; + } catch (...) { + mpi::abort_on_exception(communicator.native_handle(), + "DSPAC partition projection failed"); + } } -EdgeWeight dspac::calculate_vertex_cut(PartitionID k, const std::vector &edge_partition) { - EdgeWeight local_cost = 0; - - std::vector counted(k); - for (NodeID v = 0; v < m_input_graph.number_of_local_nodes(); ++v) { - if (m_input_graph.getNodeDegree(v) == 0) { - continue; - } - - for (EdgeID e = m_input_graph.get_first_edge(v); e < m_input_graph.get_first_invalid_edge(v); ++e) { - PartitionID p = edge_partition[e]; - if (!counted[p]) { - counted[p] = true; - ++local_cost; - } - } - - counted.clear(); - counted.resize(k); +EdgeWeight dspac::calculate_vertex_cut( + PartitionID k, + std::vector const& edge_partition) { + mpi::require_live_intracommunicator( + mpi::communicator_view{m_comm}, + "vertex cut validation requires a live intracommunicator"); + + auto minimum_k = PartitionID{}; + auto maximum_k = PartitionID{}; + mpi::check_or_abort( + MPI_Allreduce(&k, &minimum_k, 1, mpi::get_mpi_datatype(), + MPI_MIN, m_comm), + m_comm, "MPI_Allreduce(vertex cut k minimum)"); + mpi::check_or_abort( + MPI_Allreduce(&k, &maximum_k, 1, mpi::get_mpi_datatype(), + MPI_MAX, m_comm), + m_comm, "MPI_Allreduce(vertex cut k maximum)"); + + constexpr auto zero_k = PartitionID{1} << 0; + constexpr auto unrepresentable_k = PartitionID{1} << 1; + constexpr auto mismatched_k = PartitionID{1} << 2; + constexpr auto mismatched_partition_extent = PartitionID{1} << 3; + constexpr auto out_of_range_label = PartitionID{1} << 4; + + auto local_issues = PartitionID{}; + if (k == 0) { + local_issues |= zero_k; + } + if (!std::in_range(k)) { + local_issues |= unrepresentable_k; + } + if (minimum_k != maximum_k) { + local_issues |= mismatched_k; + } + auto const local_edge_count = m_input_graph.number_of_local_edges(); + if (!std::in_range(local_edge_count) || + (std::in_range(local_edge_count) && + edge_partition.size() != static_cast(local_edge_count))) { + local_issues |= mismatched_partition_extent; + } + if (std::ranges::any_of(edge_partition, + [k](PartitionID label) { return label >= k; })) { + local_issues |= out_of_range_label; + } + + auto global_issues = PartitionID{}; + mpi::check_or_abort( + MPI_Allreduce(&local_issues, &global_issues, 1, + mpi::get_mpi_datatype(), MPI_BOR, m_comm), + m_comm, "MPI_Allreduce(vertex cut validation)"); + + if ((global_issues & zero_k) != 0) { + mpi::abort_on_programming_error(m_comm, + "vertex cut requires k greater than zero"); + } + if ((global_issues & unrepresentable_k) != 0) { + mpi::abort_on_programming_error( + m_comm, "vertex cut k exceeds local size_t capacity"); + } + if ((global_issues & mismatched_k) != 0) { + mpi::abort_on_programming_error(m_comm, + "vertex cut k differs across communicator"); + } + if ((global_issues & mismatched_partition_extent) != 0) { + mpi::abort_on_programming_error( + m_comm, "vertex cut partition extent does not match local edge count"); + } + if ((global_issues & out_of_range_label) != 0) { + mpi::abort_on_programming_error( + m_comm, "vertex cut partition label is outside [0, k)"); + } + + auto local_cost = EdgeWeight{}; + auto counted = std::vector(static_cast(k)); + + for (NodeID v = 0; v < m_input_graph.number_of_local_nodes(); ++v) { + if (m_input_graph.getNodeDegree(v) == 0) { + continue; + } - assert(local_cost > 0); - --local_cost; + auto distinct_blocks = EdgeWeight{}; + for (EdgeID e = m_input_graph.get_first_edge(v); + e < m_input_graph.get_first_invalid_edge(v); ++e) { + auto const p = edge_partition[static_cast(e)]; + if (!counted[p]) { + counted[p] = true; + ++distinct_blocks; + } } - EdgeWeight global_cost; - MPI_Reduce(&local_cost, &global_cost, 1, MPI_UNSIGNED_LONG_LONG, MPI_SUM, 0, m_comm); - return global_cost; + assert(distinct_blocks > 0); + local_cost += distinct_blocks - 1; + std::ranges::fill(counted, false); + } + + return mpi::all_reduce_sum(local_cost, mpi::communicator_view{m_comm}, + "MPI_Allreduce(vertex cut)"); } void dspac::fix_cut_dominant_edges(parallel_graph_access &split_graph) { - for (NodeID v = 0; v < split_graph.number_of_local_nodes(); ++v) { - EdgeID e_vu = split_graph.get_first_edge(v); - NodeID u = split_graph.getEdgeTarget(e_vu); - - PartitionID part_v = split_graph.getNodeLabel(v); - PartitionID part_u = split_graph.getNodeLabel(u); - if (part_v != part_u) { - NodeWeight part_v_size = split_graph.getBlockSize(part_v); - NodeWeight part_u_size = split_graph.getBlockSize(part_u); - - if (part_v_size < part_u_size) { - split_graph.setNodeLabel(u, part_v); - split_graph.setBlockSize(part_v, part_v_size + 1); - split_graph.setBlockSize(part_u, part_u_size - 1); - } else { - split_graph.setNodeLabel(v, part_u); - split_graph.setBlockSize(part_v, part_v_size - 1); - split_graph.setBlockSize(part_u, part_u_size + 1); - } - } + for (NodeID v = 0; v < split_graph.number_of_local_nodes(); ++v) { + EdgeID e_vu = split_graph.get_first_edge(v); + NodeID u = split_graph.getEdgeTarget(e_vu); + + PartitionID part_v = split_graph.getNodeLabel(v); + PartitionID part_u = split_graph.getNodeLabel(u); + if (part_v != part_u) { + NodeWeight part_v_size = split_graph.getBlockSize(part_v); + NodeWeight part_u_size = split_graph.getBlockSize(part_u); + + if (part_v_size < part_u_size) { + split_graph.setNodeLabel(u, part_v); + split_graph.setBlockSize(part_v, part_v_size + 1); + split_graph.setBlockSize(part_u, part_u_size - 1); + } else { + split_graph.setNodeLabel(v, part_u); + split_graph.setBlockSize(part_v, part_v_size - 1); + split_graph.setBlockSize(part_u, part_u_size + 1); + } } - split_graph.update_block_weights(); + } + split_graph.update_block_weights(); } +} // namespace parhip diff --git a/parallel/parallel_src/lib/dspac/dspac.h b/parallel/parallel_src/lib/dspac/dspac.h index 8138190f..6acde44b 100644 --- a/parallel/parallel_src/lib/dspac/dspac.h +++ b/parallel/parallel_src/lib/dspac/dspac.h @@ -10,28 +10,37 @@ #define KAHIP_DSPAC_H #include + +#include "communication/mpi_collectives.h" #include "data_structure/parallel_graph_access.h" #include "definitions.h" +namespace parhip { class dspac { -public: - dspac(parallel_graph_access &graph, MPI_Comm comm, EdgeWeight infinity); - void construct(parallel_graph_access &split_graph); - std::vector project_partition(parallel_graph_access &split_graph, const std::vector &permutation); - EdgeWeight calculate_vertex_cut(PartitionID k, const std::vector &edge_partition); - void fix_cut_dominant_edges(parallel_graph_access &split_graph); + public: + dspac(parallel_graph_access& graph, + MPI_Comm comm, + EdgeWeight infinity, + mpi::collective_options collective_options = {}); + void construct(parallel_graph_access& split_graph); + std::vector project_partition( + parallel_graph_access& split_graph, + std::vector const& permutation); + EdgeWeight calculate_vertex_cut( + PartitionID k, + std::vector const& edge_partition); + void fix_cut_dominant_edges(parallel_graph_access& split_graph); -private: - bool assert_adjacency_lists_sorted(); - bool assert_sanity_checks(parallel_graph_access &split_graph); - bool assert_edge_range_array_ok(const std::vector &edge_range_array); - bool assert_node_range_array_ok(const std::vector &node_range_array); + private: + bool assert_sanity_checks(parallel_graph_access& split_graph); - void internal_construct(parallel_graph_access &split_graph); + void internal_construct(parallel_graph_access& split_graph, + mpi::communicator_view communicator); - MPI_Comm m_comm; - EdgeWeight m_infinity; - parallel_graph_access &m_input_graph; + MPI_Comm m_comm; + EdgeWeight m_infinity; + parallel_graph_access& m_input_graph; + mpi::collective_options m_collective_options; }; - -#endif // KAHIP_DSPAC_H +} // namespace parhip +#endif // KAHIP_DSPAC_H diff --git a/parallel/parallel_src/lib/dspac/edge_balanced_graph_io.cpp b/parallel/parallel_src/lib/dspac/edge_balanced_graph_io.cpp index b152658e..70e3f448 100644 --- a/parallel/parallel_src/lib/dspac/edge_balanced_graph_io.cpp +++ b/parallel/parallel_src/lib/dspac/edge_balanced_graph_io.cpp @@ -1,272 +1,664 @@ +#define _FILE_OFFSET_BITS 64 + /****************************************************************************** - * edge_balanced_graph_io.h + * edge_balanced_graph_io.cpp * * * Source of KaHIP -- Karlsruhe High Quality Partitioning. * Christian Schulz *****************************************************************************/ -#include -#include -#include - #include "edge_balanced_graph_io.h" -static constexpr ULONG FILE_TYPE_VERSION = 3; - -static constexpr ULONG HEADER_SIZE = 3; +#include +#include +#include +#include -static ULONG calculateFromNode(std::ifstream &in, ULONG numberOfNodes, ULONG numberOfEdges, int rank, int size); +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_failure.h" +#include "communication/mpi_fixed_broadcast.h" + +namespace parhip { +namespace { +namespace detail = edge_balanced_graph_io_detail; +using mpi::communicator_view; + +class graph_file_descriptor final { + public: + graph_file_descriptor() noexcept = default; + explicit graph_file_descriptor(int descriptor) noexcept + : descriptor_(descriptor) {} + ~graph_file_descriptor() noexcept { + if (descriptor_ >= 0) { + static_cast(::close(descriptor_)); + } + } + + graph_file_descriptor(graph_file_descriptor const&) = delete; + auto operator=(graph_file_descriptor const&) + -> graph_file_descriptor& = delete; + graph_file_descriptor(graph_file_descriptor&& other) noexcept + : descriptor_(std::exchange(other.descriptor_, -1)) {} + auto operator=(graph_file_descriptor&& other) noexcept + -> graph_file_descriptor& { + if (this != &other) { + if (descriptor_ >= 0) { + static_cast(::close(descriptor_)); + } + descriptor_ = std::exchange(other.descriptor_, -1); + } + return *this; + } + + [[nodiscard]] explicit operator bool() const noexcept { + return descriptor_ >= 0; + } + [[nodiscard]] auto get() const noexcept -> int { return descriptor_; } + [[nodiscard]] auto close() noexcept -> bool { + if (descriptor_ < 0) { + return false; + } + auto const descriptor = std::exchange(descriptor_, -1); + return ::close(descriptor) == 0; + } + + private: + int descriptor_ = -1; +}; + +[[nodiscard]] auto read_graph_exact(int descriptor, + std::span bytes, + ULONG offset) noexcept -> bool { + constexpr auto maximum_offset = + static_cast(std::numeric_limits::max()); + constexpr auto maximum_transfer = + static_cast(std::numeric_limits::max()); + while (!bytes.empty()) { + if (offset > maximum_offset) { + return false; + } + auto const transfer = std::min(bytes.size(), maximum_transfer); + auto const received = + ::pread(descriptor, bytes.data(), transfer, static_cast(offset)); + if (received < 0 && errno == EINTR) { + continue; + } + if (received <= 0) { + return false; + } + auto const count = static_cast(received); + bytes = bytes.subspan(count); + offset += static_cast(count); + } + return true; +} -static ULONG calculateToNode(std::ifstream &in, ULONG numberOfNodes, ULONG numberOfEdges, int rank, int size); +template +[[nodiscard]] auto read_graph_exact(int descriptor, + std::span values, + ULONG offset) noexcept -> bool { + static_assert(std::is_trivially_copyable_v); + return read_graph_exact(descriptor, std::as_writable_bytes(values), offset); +} -static ULONG readNumberOfEdgesInRange(std::ifstream &in, ULONG numberOfNodes, ULONG from, ULONG to); +[[nodiscard]] auto observed_file_extent(int descriptor, ULONG& extent) noexcept + -> bool { + struct stat status{}; + if (::fstat(descriptor, &status) != 0 || status.st_size < 0) { + return false; + } + auto const observed = static_cast(status.st_size); + if (observed > + static_cast(std::numeric_limits::max())) { + return false; + } + extent = static_cast(observed); + return true; +} -static ULONG readFirstEdge(std::ifstream &in, ULONG numberOfNodes, ULONG node); +[[nodiscard]] auto descriptor_has_extent(int descriptor, + ULONG expected) noexcept -> bool { + auto observed = ULONG{0}; + return observed_file_extent(descriptor, observed) && observed == expected; +} -static ULONG readFirstInvalidEdge(std::ifstream &in, ULONG numberOfNodes, ULONG node); +void require_collective_backend_success( + bool local_success, + communicator_view communicator, + std::string_view diagnostic, + std::string_view agreement_context) noexcept { + auto const local = local_success ? 1 : 0; + auto global = 0; + mpi::check_or_abort(MPI_Allreduce(&local, &global, 1, MPI_INT, MPI_MIN, + communicator.native_handle()), + communicator.native_handle(), agreement_context); + if (global == 0) { + mpi::abort_on_backend_failure(communicator.native_handle(), diagnostic); + } +} -static ULONG adjacencyListOffsetToEdgeID(ULONG numberOfNodes, ULONG offset); +void require_collective_capacity(bool local_success, + communicator_view communicator, + std::string_view boundary, + std::string_view diagnostic) noexcept { + auto const local = local_success ? 1 : 0; + auto global = 0; + mpi::check_or_abort(MPI_Allreduce(&local, &global, 1, MPI_INT, MPI_MIN, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(edge-balanced graph I/O capacity status)"); + if (global == 0) { + mpi::abort_on_capacity_failure(communicator.native_handle(), boundary, + diagnostic); + } +} -void edge_balanced_graph_io::read_binary_graph_edge_balanced(parallel_graph_access &G, const std::string &filename, - const PPartitionConfig &config, std::vector &permutation) { - int rank; - int size; - MPI_Comm_size(MPI_COMM_WORLD, &size); - MPI_Comm_rank(MPI_COMM_WORLD, &rank); - read_binary_graph_edge_balanced(G, filename, config, permutation, rank, size); +void require_collective_programming_condition( + bool local_success, + communicator_view communicator, + std::string_view diagnostic, + std::string_view agreement_context) noexcept { + auto const local = local_success ? 1 : 0; + auto global = 0; + mpi::check_or_abort(MPI_Allreduce(&local, &global, 1, MPI_INT, MPI_MIN, + communicator.native_handle()), + communicator.native_handle(), agreement_context); + if (global == 0) { + mpi::abort_on_programming_error(communicator.native_handle(), diagnostic); + } } -void edge_balanced_graph_io::read_binary_graph_edge_balanced(parallel_graph_access &G, const std::string &filename, - const PPartitionConfig &config, std::vector &permutation, int rank, int size) { - // header[0] = version number - // header[1] = number of nodes - // header[2] = number of edges - std::array header{0}; - - // read header on ROOT; if it fails, throw, otherwise broadcast this to other PEs - int success = 0; - if (rank == ROOT) { - std::ifstream headerIn(filename, std::ios::binary | std::ios::in); - if (headerIn) { - success = 1; - headerIn.read((char *) (&header[0]), 3 * sizeof(ULONG)); - } - headerIn.close(); - } +void validate_graph_communicator(parallel_graph_access& graph, + communicator_view communicator) noexcept { + auto relation = int{MPI_UNEQUAL}; + mpi::check_or_abort(MPI_Comm_compare(graph.getCommunicator(), + communicator.native_handle(), &relation), + communicator.native_handle(), + "MPI_Comm_compare(edge-balanced graph I/O communicator)"); + require_collective_programming_condition( + relation == MPI_IDENT || relation == MPI_CONGRUENT, communicator, + "edge-balanced graph I/O communicator does not match the graph", + "MPI_Allreduce(edge-balanced graph communicator agreement)"); +} - MPI_Bcast(&success, 1, MPI_INT, ROOT, MPI_COMM_WORLD); - if (success != 1) { - throw std::ios_base::failure("unable to read graph file"); - } +void validate_legacy_rank_and_size(int supplied_rank, + int supplied_size, + communicator_view communicator) noexcept { + require_collective_programming_condition( + supplied_rank == communicator.rank() && + supplied_size == communicator.size(), + communicator, + "edge-balanced graph I/O supplied rank or size does not match the " + "graph communicator", + "MPI_Allreduce(edge-balanced legacy rank and size validation)"); +} - MPI_Bcast(&header[0], header.size(), MPI_UNSIGNED_LONG_LONG, ROOT, MPI_COMM_WORLD); - ULONG version = header[0]; - ULONG n = header[1]; - ULONG m = header[2]; +void require_common_filename(std::string_view filename, + communicator_view communicator) { + auto canonical_size = std::uint64_t{0}; + if (communicator.rank() == ROOT) { + canonical_size = filename.size(); + } + mpi::broadcast_fixed(canonical_size, ROOT, communicator, + "MPI_Bcast(edge-balanced filename size)"); + if (!std::in_range(canonical_size)) { + mpi::abort_on_capacity_failure(communicator.native_handle(), + "edge-balanced graph filename", + "filename size is not representable"); + } + auto canonical = std::string(static_cast(canonical_size), '\0'); + if (communicator.rank() == ROOT) { + std::ranges::copy(filename, canonical.begin()); + } + mpi::broadcast_bounded(std::span{canonical}, ROOT, communicator, + "MPI_Bcast(edge-balanced filename)"); + require_collective_backend_success( + filename == canonical, communicator, + "edge-balanced graph filename differs across communicator", + "MPI_Allreduce(edge-balanced filename agreement)"); +} - if (rank == ROOT) { - std::cout << "n=" << n << ", m=" << m << std::endl; - } +[[nodiscard]] auto common_io_window(int configured, + communicator_view communicator) noexcept + -> int { + auto minimum = 0; + auto maximum = 0; + mpi::check_or_abort(MPI_Allreduce(&configured, &minimum, 1, MPI_INT, MPI_MIN, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(edge-balanced I/O window minimum)"); + mpi::check_or_abort(MPI_Allreduce(&configured, &maximum, 1, MPI_INT, MPI_MAX, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(edge-balanced I/O window maximum)"); + if (minimum != maximum) { + mpi::abort_on_programming_error( + communicator.native_handle(), + "edge-balanced graph I/O window differs across communicator"); + } + auto const window = detail::validated_window(minimum, communicator.size()); + if (!window.has_value()) { + mpi::abort_on_programming_error( + communicator.native_handle(), + "edge-balanced graph I/O window must be positive"); + } + return *window; +} - if (version != FILE_TYPE_VERSION) { - throw std::ios_base::failure("wrong file type version"); - } +struct node_interval final { + NodeID first = 0; + NodeID next = 0; + + [[nodiscard]] constexpr auto size() const noexcept -> NodeID { + return next - first; + } + [[nodiscard]] constexpr auto inclusive_last() const noexcept -> NodeID { + return first == next ? first : next - 1; + } +}; + +[[nodiscard]] constexpr auto validation_interval(NodeID nodes, + int rank, + int size) noexcept + -> node_interval { + return node_interval{detail::balanced_vertex_boundary(nodes, rank, size), + detail::balanced_vertex_boundary(nodes, rank + 1, size)}; +} - /* - * next, we determine the number of vertices on each PE such that the number of edges are almost evenly - * distributed - */ - std::ifstream in(filename, std::ios::binary | std::ios::in); - in.exceptions(std::ios_base::failbit | std::ios_base::badbit); - ULONG from = calculateFromNode(in, n, m, rank, size); // inclusive! - ULONG to = calculateToNode(in, n, m, rank, size); // inclusive! - ULONG numberOfLocalNodes = to - from + 1; - ULONG numberOfLocalEdges = readNumberOfEdgesInRange(in, n, from, to); - - std::cout << "peID=" << rank << ": from=" << from << ", to=" << to << ", numberOfLocalNodes=" - << numberOfLocalNodes << ", numberOfLocalEdges=" << numberOfLocalEdges << std::endl; - - in.close(); - - permutation.resize(numberOfLocalEdges); - std::iota(permutation.begin(), permutation.end(), 0); - - // to construct the vertex range array, send 'from' to all other PEs - std::vector nodeRanges(static_cast(size + 1)); - MPI_Allgather(&from, 1, MPI_UNSIGNED_LONG_LONG, &nodeRanges[0], 1, MPI_UNSIGNED_LONG_LONG, MPI_COMM_WORLD); - nodeRanges[size] = n; - - /* - * the last part, i.e. loading the graph, is the same as in parallel_graph_io::readGraphBinary() - */ - PEID windowSize = std::min(size, config.binary_io_window_size); - PEID lowPE = 0; - PEID highPE = windowSize; - - while (lowPE < size) { - if (rank >= lowPE && rank < highPE) { - std::ifstream in(filename, std::ios::binary | std::ios::in); - in.exceptions(std::ios_base::failbit | std::ios_base::badbit); - - // extract splitters for split graph construction - std::vector edgeRanges(static_cast(size + 1)); - for (PEID pe = 0; pe < size; ++pe) { - ULONG peFrom = nodeRanges[pe]; // inclusive - ULONG peTo = nodeRanges[pe + 1]; // exclusive - ULONG peLocalNodes = peTo - peFrom; - - ULONG peStartPos = (HEADER_SIZE + peFrom) * sizeof(ULONG); - NodeID peFirstNodeOffset, peLastNodeOffset; - - in.seekg(peStartPos); - in.read((char *) &peFirstNodeOffset, sizeof(ULONG)); - in.seekg(peStartPos + peLocalNodes * sizeof(ULONG)); - in.read((char *) &peLastNodeOffset, sizeof(ULONG)); - - EdgeID peLocalEdges = (peLastNodeOffset - peFirstNodeOffset) / sizeof(ULONG); - edgeRanges[pe + 1] = edgeRanges[pe] + peLocalEdges; - } - - // load and construction, just like parallel_graph_io::readGraphBinary() - ULONG startPos = (HEADER_SIZE + from) * sizeof(ULONG); - NodeID *vertexOffsets = new NodeID[numberOfLocalNodes + 1]; - in.seekg(startPos); - in.read((char *) vertexOffsets, static_cast((numberOfLocalNodes + 1) * sizeof(ULONG))); - - ULONG edgeStartPos = vertexOffsets[0]; - EdgeID numReads = vertexOffsets[numberOfLocalNodes] - vertexOffsets[0]; - EdgeID numEdgesToRead = numReads / sizeof(ULONG); - EdgeID *edges = new EdgeID[numEdgesToRead]; - in.seekg(edgeStartPos); - in.read((char *) edges, static_cast(numEdgesToRead * sizeof(ULONG))); - - G.start_construction(numberOfLocalNodes, numberOfLocalEdges, n, m); - G.set_range(from, to); - G.set_range_array(nodeRanges); - G.set_edge_range_array(edgeRanges); - - ULONG pos = 0; - for (NodeID i = 0; i < numberOfLocalNodes; ++i) { - NodeID node = G.new_node(); - G.setNodeWeight(node, 1); - G.setNodeLabel(node, from + node); - G.setSecondPartitionIndex(node, 0); - - NodeID degree = (vertexOffsets[i + 1] - vertexOffsets[i]) / sizeof(ULONG); - - std::sort(permutation.begin() + pos, permutation.begin() + pos + degree, [&](EdgeID a, EdgeID b) { - return edges[a] < edges[b]; - }); - std::sort(edges + pos, edges + pos + degree); - - for (ULONG j = 0; j < degree; ++j, ++pos) { - NodeID target = edges[pos]; - EdgeID edge = G.new_edge(node, target); - G.setEdgeWeight(edge, 1); - } - } - - G.finish_construction(); - in.close(); - - delete[] edges; - delete[] vertexOffsets; - } +[[nodiscard]] constexpr auto offset_table_position(NodeID node) noexcept + -> std::optional { + auto const word = detail::checked_add(detail::header_words, node); + return word.has_value() + ? detail::checked_multiply(*word, ULONG{sizeof(ULONG)}) + : std::nullopt; +} - lowPE += windowSize; - highPE += windowSize; - MPI_Barrier(MPI_COMM_WORLD); - } +[[nodiscard]] auto read_header(std::string const& filename, + communicator_view communicator, + std::array& header, + ULONG& file_extent) noexcept -> bool { + if (communicator.rank() != ROOT) { + return true; + } + auto descriptor = graph_file_descriptor{::open(filename.c_str(), O_RDONLY)}; + if (!descriptor) { + return false; + } + auto success = + observed_file_extent(descriptor.get(), file_extent) && + read_graph_exact(descriptor.get(), std::span{header}, ULONG{0}); + auto const close_success = descriptor.close(); + return success && close_success; } -/** - * Calculates the first node that should be on the given PE such that edges are roughly balanced across all PEs. - * To achieve this, we use binary search to find the first node such that enough edges are incident to nodes before - * that one. - */ -static ULONG calculateFromNode(std::ifstream &in, ULONG numberOfNodes, ULONG numberOfEdges, int rank, int size) { - if (rank == 0) { - return 0; +[[nodiscard]] auto read_node_boundary(int descriptor, + NodeID nodes, + EdgeID edges, + int boundary, + int size, + detail::binary_layout layout, + NodeID& result) noexcept -> bool { + if (edges == 0) { + result = detail::balanced_vertex_boundary(nodes, boundary, size); + return true; + } + if (boundary <= 0) { + result = 0; + return true; + } + if (boundary >= size) { + result = nodes; + return true; + } + + auto const target = detail::balanced_edge_target(edges, boundary, size); + auto low = NodeID{0}; + auto high = nodes; + while (low < high) { + auto const middle = low + (high - low) / 2; + auto const position = offset_table_position(middle); + auto offset = ULONG{0}; + if (!position.has_value() || + !read_graph_exact(descriptor, std::span{&offset, 1}, + *position) || + !detail::offsets_are_valid(std::span{&offset, 1}, layout, + false, false)) { + return false; } - if (rank == size) { - return numberOfNodes; - } - - // calculate the number of edges that should come before the first edge on this PE - ULONG chunk = numberOfEdges / size; - ULONG remainder = numberOfEdges % size; - ULONG target = rank * chunk + std::min(static_cast(rank), remainder); - - // find the first node that is incident to an edge greater than target - // this should be the first edge on this PE - // a.first = node id - // a.second = first edge id - std::pair a{0, 0}; - std::pair b{numberOfNodes - 1, numberOfEdges - 1}; - - while (b.first - a.first > 1) { - std::pair mid; - mid.first = (a.first + b.first) / 2; - mid.second = readFirstEdge(in, numberOfNodes, mid.first); - - if (mid.second < target) { - a = mid; - } else { - b = mid; - } - - assert(b.first >= a.first); + auto const prefix = (offset - layout.adjacency_begin) / sizeof(ULONG); + if (prefix < target) { + low = middle + 1; + } else { + high = middle; } + } + result = low; + return true; +} - assert(a.second <= target && target <= b.second); - assert(b.first < numberOfNodes); - return b.first; +void validate_offset_slices(std::span local_offsets, + detail::binary_layout layout, + communicator_view communicator, + std::string_view context) { + auto const local = + std::array{local_offsets.front(), local_offsets.back()}; + auto gathered = std::vector( + static_cast(communicator.size()) * local.size()); + mpi::check_or_abort( + MPI_Allgather(local.data(), static_cast(local.size()), MPI_UINT64_T, + gathered.data(), static_cast(local.size()), + MPI_UINT64_T, communicator.native_handle()), + communicator.native_handle(), context); + + auto valid = gathered.front() == layout.adjacency_begin; + for (auto rank = 1; valid && rank < communicator.size(); ++rank) { + auto const previous = static_cast(rank - 1) * local.size(); + auto const current = static_cast(rank) * local.size(); + valid = gathered[previous + 1] == gathered[current]; + } + valid = valid && gathered.back() == layout.file_extent; + require_collective_backend_success( + valid, communicator, "edge-balanced binary offset table is invalid", + "MPI_Allreduce(edge-balanced offset slice validation)"); } -/** - * Same as calculateFromNode(), but calculates that last node that should be on the given PE. - */ -static ULONG calculateToNode(std::ifstream &in, ULONG numberOfNodes, ULONG numberOfEdges, int rank, int size) { - return calculateFromNode(in, numberOfNodes, numberOfEdges, rank + 1, size) - 1; +[[nodiscard]] auto gather_node_ranges(NodeID first, + NodeID nodes, + communicator_view communicator) + -> std::vector { + auto ranges = + std::vector(static_cast(communicator.size()) + 1); + mpi::check_or_abort( + MPI_Allgather(&first, 1, MPI_UNSIGNED_LONG_LONG, ranges.data(), 1, + MPI_UNSIGNED_LONG_LONG, communicator.native_handle()), + communicator.native_handle(), "MPI_Allgather(edge-balanced node ranges)"); + ranges.back() = nodes; + auto const valid = ranges.front() == 0 && ranges.back() == nodes && + std::ranges::is_sorted(ranges) && + std::ranges::all_of(ranges, [=](auto boundary) { + return boundary <= nodes; + }); + require_collective_backend_success( + valid, communicator, "edge-balanced node ranges are invalid", + "MPI_Allreduce(edge-balanced node range validation)"); + return ranges; } -/** - * Determines the number of edges that are incident to nodes in [from, to]. - */ -static ULONG readNumberOfEdgesInRange(std::ifstream &in, ULONG numberOfNodes, ULONG from, ULONG to) { - if (from == to + 1) { - return 0; +[[nodiscard]] auto gather_edge_ranges(EdgeID local_edges, + EdgeID global_edges, + communicator_view communicator) + -> std::vector { + auto counts = + std::vector(static_cast(communicator.size())); + mpi::check_or_abort( + MPI_Allgather(&local_edges, 1, MPI_UNSIGNED_LONG_LONG, counts.data(), 1, + MPI_UNSIGNED_LONG_LONG, communicator.native_handle()), + communicator.native_handle(), "MPI_Allgather(edge-balanced edge counts)"); + + auto ranges = std::vector(counts.size() + 1, EdgeID{0}); + auto representable = true; + for (auto rank = std::size_t{0}; rank < counts.size(); ++rank) { + representable = + representable && + counts[rank] <= std::numeric_limits::max() - ranges[rank]; + if (representable) { + ranges[rank + 1] = ranges[rank] + counts[rank]; } - ULONG firstEdge = readFirstEdge(in, numberOfNodes, from); - ULONG firstInvalidEdge = readFirstInvalidEdge(in, numberOfNodes, to); - return firstInvalidEdge - firstEdge; - + } + require_collective_capacity(representable, communicator, + "edge-balanced graph input", + "global edge prefix sum is not representable"); + require_collective_backend_success( + ranges.back() == global_edges, communicator, + "edge-balanced binary graph edge count does not match its header", + "MPI_Allreduce(edge-balanced global edge validation)"); + return ranges; } -/** - * Reads G.get_first_edge(NodeID) from a binary graph file. - */ -static ULONG readFirstEdge(std::ifstream &in, ULONG numberOfNodes, ULONG node) { - assert(node <= numberOfNodes); - - ULONG pos = (HEADER_SIZE + node) * sizeof(ULONG); - in.seekg(pos); - - ULONG entry = 0; - in.read((char *) (&entry), sizeof(ULONG)); - return adjacencyListOffsetToEdgeID(numberOfNodes, entry); +void construct_graph(parallel_graph_access& graph, + NodeID global_nodes, + EdgeID global_edges, + node_interval interval, + std::span offsets, + std::span adjacency, + std::vector& node_ranges, + std::vector& edge_ranges) { + graph.start_construction(interval.size(), + static_cast(adjacency.size()), global_nodes, + global_edges); + graph.set_range(interval.first, interval.inclusive_last()); + graph.set_range_array(node_ranges); + graph.set_edge_range_array(edge_ranges); + + auto edge_position = std::size_t{0}; + for (auto local = NodeID{0}; local < interval.size(); ++local) { + auto const source = graph.new_node(); + graph.setNodeWeight(source, 1); + graph.setNodeLabel(source, interval.first + source); + graph.setSecondPartitionIndex(source, 0); + auto const index = static_cast(local); + auto const degree = static_cast( + (offsets[index + 1] - offsets[index]) / sizeof(ULONG)); + for (auto local_edge = std::size_t{0}; local_edge < degree; + ++local_edge, ++edge_position) { + auto const edge = graph.new_edge(source, adjacency[edge_position]); + graph.setEdgeWeight(edge, 1); + } + } + graph.finish_construction(); +} +} // namespace + +void edge_balanced_graph_io::read_binary_graph_edge_balanced( + parallel_graph_access& graph, + std::string const& filename, + PPartitionConfig const& config, + std::vector& permutation, + mpi::communicator_view borrowed) { + mpi::require_live_intracommunicator( + borrowed, "edge-balanced graph input requires a live intracommunicator"); + auto operation = mpi::communicator{borrowed}; + auto const communicator = operation.view(); + validate_graph_communicator(graph, communicator); + + try { + require_common_filename(filename, communicator); + auto const window = + common_io_window(config.binary_io_window_size, communicator); + + auto header = std::array{}; + auto observed_extent = ULONG{0}; + auto const local_header_success = + read_header(filename, communicator, header, observed_extent); + require_collective_backend_success( + local_header_success, communicator, + "unable to open or read edge-balanced binary graph header", + "MPI_Allreduce(edge-balanced header read status)"); + mpi::broadcast_fixed(std::span{header}, ROOT, communicator, + "MPI_Bcast(edge-balanced graph header)"); + mpi::broadcast_fixed(observed_extent, ROOT, communicator, + "MPI_Bcast(edge-balanced graph file extent)"); + + auto const version = header[0]; + auto const global_nodes = NodeID{header[1]}; + auto const global_edges = EdgeID{header[2]}; + require_collective_backend_success( + version == detail::file_type_version, communicator, + "unsupported edge-balanced binary graph version", + "MPI_Allreduce(edge-balanced header version validation)"); + + auto const layout = detail::make_binary_layout(global_nodes, global_edges); + require_collective_capacity( + layout.has_value() && + layout->file_extent <= + static_cast(std::numeric_limits::max()), + communicator, "edge-balanced graph input", + "binary graph byte layout is not representable"); + require_collective_backend_success( + detail::file_extent_is_valid(observed_extent, *layout), communicator, + "edge-balanced binary graph has an invalid file extent", + "MPI_Allreduce(edge-balanced file extent validation)"); + + auto const rank = communicator.rank(); + auto const size = communicator.size(); + auto const validation = validation_interval(global_nodes, rank, size); + auto const validation_position = offset_table_position(validation.first); + auto const validation_size_is_representable = + std::in_range(validation.size()) && + validation.size() < std::numeric_limits::max(); + require_collective_capacity( + validation_position.has_value() && validation_size_is_representable, + communicator, "edge-balanced graph input", + "offset validation range is not representable"); + + auto validation_offsets = + std::vector(static_cast(validation.size()) + 1); + auto first_node = NodeID{0}; + for (auto low = 0; low < size; low += window) { + auto const high = std::min(size, low + window); + auto const active = rank >= low && rank < high; + auto descriptor = graph_file_descriptor{ + active ? ::open(filename.c_str(), O_RDONLY) : -1}; + require_collective_backend_success( + !active || descriptor, communicator, + "unable to open edge-balanced binary graph payload", + "MPI_Allreduce(edge-balanced validation open status)"); + + auto local_success = true; + if (active) { + local_success = + descriptor_has_extent(descriptor.get(), layout->file_extent) && + read_graph_exact(descriptor.get(), + std::span{validation_offsets}, + *validation_position) && + detail::offsets_are_valid(validation_offsets, *layout, false, + false) && + read_node_boundary(descriptor.get(), global_nodes, global_edges, + rank, size, *layout, first_node); + auto const close_success = descriptor.close(); + local_success = local_success && close_success; + } + require_collective_backend_success( + local_success, communicator, + "edge-balanced binary graph offset validation failed", + "MPI_Allreduce(edge-balanced offset validation read status)"); + } + validate_offset_slices( + validation_offsets, *layout, communicator, + "MPI_Allgather(edge-balanced validation offset slices)"); + + auto node_ranges = + gather_node_ranges(first_node, global_nodes, communicator); + auto const interval = + node_interval{node_ranges[static_cast(rank)], + node_ranges[static_cast(rank) + 1]}; + auto const local_position = offset_table_position(interval.first); + auto const local_size_is_representable = + std::in_range(interval.size()) && + interval.size() < std::numeric_limits::max(); + require_collective_capacity( + local_position.has_value() && local_size_is_representable, communicator, + "edge-balanced graph input", "local graph range is not representable"); + + auto local_offsets = + std::vector(static_cast(interval.size()) + 1); + auto adjacency = std::vector{}; + auto local_edges = EdgeID{0}; + for (auto low = 0; low < size; low += window) { + auto const high = std::min(size, low + window); + auto const active = rank >= low && rank < high; + auto descriptor = graph_file_descriptor{ + active ? ::open(filename.c_str(), O_RDONLY) : -1}; + require_collective_backend_success( + !active || descriptor, communicator, + "unable to open edge-balanced binary graph payload", + "MPI_Allreduce(edge-balanced payload open status)"); + + auto local_success = true; + if (active) { + local_success = + descriptor_has_extent(descriptor.get(), layout->file_extent) && + read_graph_exact(descriptor.get(), std::span{local_offsets}, + *local_position) && + detail::offsets_are_valid(local_offsets, *layout, false, false); + if (local_success) { + local_edges = + (local_offsets.back() - local_offsets.front()) / sizeof(ULONG); + local_success = std::in_range(local_edges); + } + if (local_success) { + adjacency.resize(static_cast(local_edges)); + local_success = + read_graph_exact(descriptor.get(), std::span{adjacency}, + local_offsets.front()) && + detail::targets_are_valid(adjacency, global_nodes); + } + auto const close_success = descriptor.close(); + local_success = local_success && close_success; + } + require_collective_backend_success( + local_success, communicator, + "edge-balanced binary graph payload validation failed", + "MPI_Allreduce(edge-balanced payload read status)"); + } + validate_offset_slices( + local_offsets, *layout, communicator, + "MPI_Allgather(edge-balanced payload offset slices)"); + auto edge_ranges = + gather_edge_ranges(local_edges, global_edges, communicator); + + auto local_permutation = std::vector(adjacency.size()); + require_collective_backend_success( + detail::canonicalize_adjacency(local_offsets, adjacency, + local_permutation), + communicator, + "edge-balanced binary graph adjacency canonicalization failed", + "MPI_Allreduce(edge-balanced canonicalization status)"); + + permutation = std::move(local_permutation); + construct_graph(graph, global_nodes, global_edges, interval, local_offsets, + adjacency, node_ranges, edge_ranges); + } catch (...) { + mpi::abort_on_exception(communicator.native_handle(), + "edge-balanced binary graph input failed"); + } } -/** - * Translates a vertex offset read from a binary graph file to an EdgeID. - */ -static ULONG adjacencyListOffsetToEdgeID(ULONG numberOfNodes, ULONG offset) { - return (offset / sizeof(ULONG)) - HEADER_SIZE - (numberOfNodes + 1); +void edge_balanced_graph_io::read_binary_graph_edge_balanced( + parallel_graph_access& graph, + std::string const& filename, + PPartitionConfig const& config, + std::vector& permutation, + int supplied_rank, + int supplied_size) { + auto const communicator = mpi::communicator_view{graph.getCommunicator()}; + mpi::require_live_intracommunicator( + communicator, + "edge-balanced graph input requires a live graph communicator"); + validate_legacy_rank_and_size(supplied_rank, supplied_size, communicator); + read_binary_graph_edge_balanced(graph, filename, config, permutation, + communicator); } -/** - * Reads G.get_first_invalid_edge(NodeID) from a binary graph file. - */ -static ULONG readFirstInvalidEdge(std::ifstream &in, ULONG numberOfNodes, ULONG node) { - return readFirstEdge(in, numberOfNodes, node + 1); +void edge_balanced_graph_io::read_binary_graph_edge_balanced( + parallel_graph_access& graph, + std::string const& filename, + PPartitionConfig const& config, + std::vector& permutation) { + read_binary_graph_edge_balanced( + graph, filename, config, permutation, + mpi::communicator_view{graph.getCommunicator()}); } +} // namespace parhip diff --git a/parallel/parallel_src/lib/dspac/edge_balanced_graph_io.h b/parallel/parallel_src/lib/dspac/edge_balanced_graph_io.h index 62222cb0..c65d055e 100644 --- a/parallel/parallel_src/lib/dspac/edge_balanced_graph_io.h +++ b/parallel/parallel_src/lib/dspac/edge_balanced_graph_io.h @@ -8,20 +8,244 @@ #ifndef KAHIP_EDGEBALANCED_GRAPH_IO_H #define KAHIP_EDGEBALANCED_GRAPH_IO_H -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include "definitions.h" +#include "communication/mpi_handles.h" #include "data_structure/parallel_graph_access.h" +#include "definitions.h" #include "partition_config.h" +namespace parhip { +namespace edge_balanced_graph_io_detail { +inline constexpr ULONG file_type_version = 3; +inline constexpr ULONG header_words = 3; + +static_assert(sizeof(ULONG) == 8, + "KaHIP binary graph version 3 stores 64-bit words"); + +struct binary_layout final { + ULONG adjacency_begin = 0; + ULONG file_extent = 0; + + auto operator==(binary_layout const&) const -> bool = default; +}; + +[[nodiscard]] constexpr auto checked_add(ULONG left, ULONG right) noexcept + -> std::optional { + if (right > std::numeric_limits::max() - left) { + return std::nullopt; + } + return left + right; +} + +[[nodiscard]] constexpr auto checked_multiply(ULONG left, ULONG right) noexcept + -> std::optional { + if (left != 0 && right > std::numeric_limits::max() / left) { + return std::nullopt; + } + return left * right; +} + +[[nodiscard]] constexpr auto make_binary_layout(NodeID nodes, + EdgeID edges) noexcept + -> std::optional { + auto const offset_words = checked_add(nodes, ULONG{1}); + auto const words_before_adjacency = + offset_words.has_value() ? checked_add(header_words, *offset_words) + : std::nullopt; + auto const adjacency_begin = + words_before_adjacency.has_value() + ? checked_multiply(*words_before_adjacency, ULONG{sizeof(ULONG)}) + : std::nullopt; + auto const adjacency_bytes = checked_multiply(edges, ULONG{sizeof(ULONG)}); + auto const file_extent = + adjacency_begin.has_value() && adjacency_bytes.has_value() + ? checked_add(*adjacency_begin, *adjacency_bytes) + : std::nullopt; + if (!adjacency_begin.has_value() || !file_extent.has_value()) { + return std::nullopt; + } + return binary_layout{*adjacency_begin, *file_extent}; +} + +[[nodiscard]] constexpr auto file_extent_is_valid(ULONG observed_extent, + binary_layout layout) noexcept + -> bool { + return observed_extent == layout.file_extent; +} + +[[nodiscard]] inline auto offsets_are_valid(std::span offsets, + binary_layout layout, + bool require_adjacency_begin, + bool require_file_extent) noexcept + -> bool { + if (offsets.empty() || !std::ranges::is_sorted(offsets)) { + return false; + } + auto const in_layout = std::ranges::all_of(offsets, [&](auto offset) { + return offset >= layout.adjacency_begin && offset <= layout.file_extent && + (offset - layout.adjacency_begin) % sizeof(ULONG) == 0; + }); + return in_layout && + (!require_adjacency_begin || + offsets.front() == layout.adjacency_begin) && + (!require_file_extent || offsets.back() == layout.file_extent); +} + +[[nodiscard]] inline auto targets_are_valid(std::span targets, + NodeID nodes) noexcept -> bool { + return std::ranges::all_of(targets, + [=](auto target) { return target < nodes; }); +} + +[[nodiscard]] constexpr auto validated_window(int configured, + int communicator_size) noexcept + -> std::optional { + if (configured <= 0 || communicator_size <= 0) { + return std::nullopt; + } + return std::min(configured, communicator_size); +} + +[[nodiscard]] constexpr auto balanced_vertex_boundary(NodeID nodes, + int boundary, + int size) noexcept + -> NodeID { + if (boundary <= 0) { + return 0; + } + if (boundary >= size) { + return nodes; + } + auto const divisor = static_cast(size); + auto const quotient = nodes / divisor; + auto const remainder = nodes % divisor; + auto const index = static_cast(boundary); + return quotient * index + std::min(index, remainder); +} + +[[nodiscard]] constexpr auto balanced_edge_target(EdgeID edges, + int boundary, + int size) noexcept -> EdgeID { + if (boundary <= 0) { + return 0; + } + if (boundary >= size) { + return edges; + } + auto const divisor = static_cast(size); + auto const quotient = edges / divisor; + auto const remainder = edges % divisor; + auto const index = static_cast(boundary); + return quotient * index + std::min(index, remainder); +} + +[[nodiscard]] inline auto node_ranges_from_offsets( + std::span offsets, + EdgeID edges, + int size) -> std::vector { + if (offsets.empty() || size <= 0) { + return {}; + } + auto const nodes = static_cast(offsets.size() - 1); + auto ranges = std::vector(static_cast(size) + 1); + for (auto boundary = 0; boundary <= size; ++boundary) { + if (edges == 0) { + ranges[static_cast(boundary)] = + balanced_vertex_boundary(nodes, boundary, size); + continue; + } + if (boundary == size) { + ranges.back() = nodes; + continue; + } + auto const target = balanced_edge_target(edges, boundary, size); + auto const position = std::ranges::lower_bound( + offsets, target, std::less<>{}, [&](auto offset) { + return (offset - offsets.front()) / sizeof(ULONG); + }); + ranges[static_cast(boundary)] = + static_cast(std::ranges::distance(offsets.begin(), position)); + } + return ranges; +} + +[[nodiscard]] inline auto canonicalize_adjacency(std::span offsets, + std::span adjacency, + std::span permutation) + -> bool { + if (offsets.empty() || adjacency.size() != permutation.size() || + !std::in_range(adjacency.size()) || + !std::ranges::is_sorted(offsets)) { + return false; + } + auto const adjacency_bytes = checked_multiply( + static_cast(adjacency.size()), ULONG{sizeof(ULONG)}); + if (!adjacency_bytes.has_value() || offsets.back() < offsets.front() || + offsets.back() - offsets.front() != *adjacency_bytes || + !std::ranges::all_of(offsets, [&](auto offset) { + return offset >= offsets.front() && + (offset - offsets.front()) % sizeof(ULONG) == 0; + })) { + return false; + } + + std::iota(permutation.begin(), permutation.end(), EdgeID{0}); + auto position = std::size_t{0}; + for (auto local_node = std::size_t{0}; local_node + 1 < offsets.size(); + ++local_node) { + auto const degree = static_cast( + (offsets[local_node + 1] - offsets[local_node]) / sizeof(ULONG)); + if (degree > adjacency.size() - position) { + return false; + } + auto const first = + permutation.begin() + static_cast(position); + auto const next = first + static_cast(degree); + std::sort(first, next, [&](EdgeID left, EdgeID right) { + return adjacency[static_cast(left)] < + adjacency[static_cast(right)]; + }); + std::ranges::sort(adjacency.subspan(position, degree)); + position += degree; + } + return position == adjacency.size(); +} +} // namespace edge_balanced_graph_io_detail + class edge_balanced_graph_io { -public: - static void read_binary_graph_edge_balanced(parallel_graph_access &G, const std::string &filename, - const PPartitionConfig &config, std::vector &permutation, int rank, int size); + public: + static void read_binary_graph_edge_balanced( + parallel_graph_access& graph, + std::string const& filename, + PPartitionConfig const& config, + std::vector& permutation, + mpi::communicator_view communicator); + + // Compatibility shims retain existing callers while all rank and size + // decisions inside the operation are derived from the graph communicator. + static void read_binary_graph_edge_balanced(parallel_graph_access& graph, + std::string const& filename, + PPartitionConfig const& config, + std::vector& permutation, + int supplied_rank, + int supplied_size); - static void read_binary_graph_edge_balanced(parallel_graph_access &G, const std::string &filename, - const PPartitionConfig &config, std::vector &permutation); + static void read_binary_graph_edge_balanced(parallel_graph_access& graph, + std::string const& filename, + PPartitionConfig const& config, + std::vector& permutation); }; -#endif // KAHIP_EDGEBALANCED_GRAPH_IO_H +} // namespace parhip +#endif // KAHIP_EDGEBALANCED_GRAPH_IO_H diff --git a/parallel/parallel_src/lib/io/parallel_graph_io.cpp b/parallel/parallel_src/lib/io/parallel_graph_io.cpp index 5d945867..3e8eec59 100644 --- a/parallel/parallel_src/lib/io/parallel_graph_io.cpp +++ b/parallel/parallel_src/lib/io/parallel_graph_io.cpp @@ -7,878 +7,1438 @@ #define _FILE_OFFSET_BITS 64 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include #include -#include -#include +#include +#include #include -#include +#include +#include +#include +#include #include +#include "communication/mpi_adapter.h" +#include "communication/mpi_failure.h" +#include "communication/mpi_fixed_broadcast.h" #include "parallel_graph_io.h" #include "tools/helpers.h" - +namespace parhip { const ULONG fileTypeVersionNumber = 3; const ULONG header_count = 3; +namespace { +using mpi::communicator_view; + +class graph_file_descriptor final { + public: + graph_file_descriptor() noexcept = default; + explicit graph_file_descriptor(int descriptor) noexcept + : descriptor_(descriptor) {} + ~graph_file_descriptor() noexcept { + if (descriptor_ >= 0) { + static_cast(::close(descriptor_)); + } + } + + graph_file_descriptor(graph_file_descriptor const&) = delete; + auto operator=(graph_file_descriptor const&) + -> graph_file_descriptor& = delete; + graph_file_descriptor(graph_file_descriptor&& other) noexcept + : descriptor_(std::exchange(other.descriptor_, -1)) {} + auto operator=(graph_file_descriptor&& other) noexcept + -> graph_file_descriptor& { + if (this != &other) { + if (descriptor_ >= 0) { + static_cast(::close(descriptor_)); + } + descriptor_ = std::exchange(other.descriptor_, -1); + } + return *this; + } + + [[nodiscard]] explicit operator bool() const noexcept { + return descriptor_ >= 0; + } + [[nodiscard]] auto get() const noexcept -> int { return descriptor_; } + [[nodiscard]] auto close() noexcept -> bool { + if (descriptor_ < 0) { + return false; + } + auto const descriptor = std::exchange(descriptor_, -1); + return ::close(descriptor) == 0; + } + + private: + int descriptor_ = -1; +}; + +[[nodiscard]] auto read_graph_exact(int descriptor, + std::span bytes, + std::uint64_t offset) noexcept -> bool { + constexpr auto maximum_offset = + static_cast(std::numeric_limits::max()); + constexpr auto maximum_transfer = + static_cast(std::numeric_limits::max()); + while (!bytes.empty()) { + if (offset > maximum_offset) { + return false; + } + auto const transfer = std::min(bytes.size(), maximum_transfer); + auto const received = + ::pread(descriptor, bytes.data(), transfer, static_cast(offset)); + if (received < 0 && errno == EINTR) { + continue; + } + if (received <= 0) { + return false; + } + auto const count = static_cast(received); + bytes = bytes.subspan(count); + offset += static_cast(count); + } + return true; +} -parallel_graph_io::parallel_graph_io() { - +template +[[nodiscard]] auto read_graph_exact(int descriptor, + std::span values, + std::uint64_t offset) noexcept -> bool { + static_assert(std::is_trivially_copyable_v); + return read_graph_exact(descriptor, std::as_writable_bytes(values), offset); } -parallel_graph_io::~parallel_graph_io() { - +[[nodiscard]] auto write_graph_exact(int descriptor, + std::span bytes, + std::uint64_t offset) noexcept -> bool { + constexpr auto maximum_offset = + static_cast(std::numeric_limits::max()); + constexpr auto maximum_transfer = + static_cast(std::numeric_limits::max()); + while (!bytes.empty()) { + if (offset > maximum_offset) { + return false; + } + auto const transfer = std::min(bytes.size(), maximum_transfer); + auto const written = ::pwrite(descriptor, bytes.data(), transfer, + static_cast(offset)); + if (written < 0 && errno == EINTR) { + continue; + } + if (written <= 0) { + return false; + } + auto const count = static_cast(written); + bytes = bytes.subspan(count); + offset += static_cast(count); + } + return true; } -int parallel_graph_io::readGraphWeighted(PPartitionConfig & config, - parallel_graph_access & G, - std::string filename, - PEID peID, PEID comm_size, MPI_Comm communicator) { +template +[[nodiscard]] auto write_graph_exact(int descriptor, + std::span values, + std::uint64_t offset) noexcept -> bool { + static_assert(std::is_trivially_copyable_v); + return write_graph_exact(descriptor, std::as_bytes(values), offset); +} - std::string metis_ending(".graph"); - std::string bin_ending(".bgf"); +void require_graph_io_success(bool local_success, + communicator_view communicator, + std::string_view diagnostic, + std::string_view agreement_context) noexcept { + auto const local = local_success ? 1 : 0; + auto global = 0; + mpi::check_or_abort(MPI_Allreduce(&local, &global, 1, MPI_INT, MPI_MIN, + communicator.native_handle()), + communicator.native_handle(), agreement_context); + if (global == 0) { + mpi::abort_on_backend_failure(communicator.native_handle(), diagnostic); + } +} - if( hasEnding(filename, metis_ending) ) { - std::stringstream ss; - ss << filename << bin_ending; - if(file_exists(ss.str())) { - return readGraphBinary(config, G, ss.str(), peID, comm_size, communicator); - } else { - return readGraphWeightedFlexible(G, filename, peID, comm_size, communicator); - } - } +void require_graph_capacity(bool local_success, + communicator_view communicator, + std::string_view boundary, + std::string_view diagnostic) noexcept { + auto const local = local_success ? 1 : 0; + auto global = 0; + mpi::check_or_abort(MPI_Allreduce(&local, &global, 1, MPI_INT, MPI_MIN, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(graph I/O capacity status)"); + if (global == 0) { + mpi::abort_on_capacity_failure(communicator.native_handle(), boundary, + diagnostic); + } +} - if( hasEnding(filename, bin_ending) ) { - return readGraphBinary(config, G, filename, peID, comm_size, communicator); - } +void validate_graph_call(parallel_graph_access& graph, + PEID supplied_rank, + PEID supplied_size, + communicator_view communicator) noexcept { + int relation = MPI_UNEQUAL; + mpi::check_or_abort(MPI_Comm_compare(graph.getCommunicator(), + communicator.native_handle(), &relation), + communicator.native_handle(), + "MPI_Comm_compare(graph I/O communicator)"); + auto const local_valid = supplied_rank == communicator.rank() && + supplied_size == communicator.size() && + (relation == MPI_IDENT || relation == MPI_CONGRUENT); + auto const local = local_valid ? 1 : 0; + auto global = 0; + mpi::check_or_abort(MPI_Allreduce(&local, &global, 1, MPI_INT, MPI_MIN, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(graph I/O call validation)"); + if (global == 0) { + mpi::abort_on_programming_error( + communicator.native_handle(), + "graph I/O rank, size, or communicator does not match the graph"); + } +} + +void require_graph_filename(std::string_view filename, + communicator_view communicator, + std::string_view diagnostic) { + auto size = std::uint64_t{0}; + if (communicator.rank() == ROOT) { + size = filename.size(); + } + mpi::broadcast_fixed(size, ROOT, communicator, + "MPI_Bcast(graph I/O filename size)"); + if (!std::in_range(size)) { + mpi::abort_on_capacity_failure(communicator.native_handle(), + "graph I/O filename", + "filename size is not representable"); + } + auto canonical = std::string(static_cast(size), '\0'); + if (communicator.rank() == ROOT) { + std::ranges::copy(filename, canonical.begin()); + } + mpi::broadcast_bounded(std::span{canonical}, ROOT, communicator, + "MPI_Bcast(graph I/O filename)"); + require_graph_io_success(filename == canonical, communicator, diagnostic, + "MPI_Allreduce(graph I/O filename agreement)"); +} - //non of both is true -- try metis format - return readGraphWeightedFlexible(G, filename, peID, comm_size, communicator); +[[nodiscard]] auto graph_window(int configured, + communicator_view communicator) noexcept + -> int { + auto const local = std::max(1, std::min(configured, communicator.size())); + auto minimum = 0; + auto maximum = 0; + mpi::check_or_abort(MPI_Allreduce(&local, &minimum, 1, MPI_INT, MPI_MIN, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(graph I/O window minimum)"); + mpi::check_or_abort(MPI_Allreduce(&local, &maximum, 1, MPI_INT, MPI_MAX, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(graph I/O window maximum)"); + if (minimum != maximum) { + mpi::abort_on_programming_error( + communicator.native_handle(), + "graph I/O window differs across communicator"); + } + return minimum; } -int parallel_graph_io::readGraphWeightedFlexible(parallel_graph_access & G, - std::string filename, - PEID peID, PEID comm_size, MPI_Comm communicator) { - std::string line; +[[nodiscard]] constexpr auto checked_add(ULONG left, ULONG right) noexcept + -> std::optional { + if (right > std::numeric_limits::max() - left) { + return std::nullopt; + } + return left + right; +} - // open file for reading - std::ifstream in(filename.c_str()); - if (!in) { - std::cerr << "Error opening " << filename << std::endl; - return 1; - } +[[nodiscard]] constexpr auto checked_multiply(ULONG left, ULONG right) noexcept + -> std::optional { + if (left != 0 && right > std::numeric_limits::max() / left) { + return std::nullopt; + } + return left * right; +} - NodeID nmbNodes; - EdgeID nmbEdges; +[[nodiscard]] constexpr auto binary_adjacency_base(ULONG nodes) noexcept + -> std::optional { + auto const offset_count = checked_add(nodes, ULONG{1}); + if (!offset_count.has_value()) { + return std::nullopt; + } + auto const word_count = checked_add(header_count, *offset_count); + return word_count.has_value() + ? checked_multiply(*word_count, ULONG{sizeof(ULONG)}) + : std::nullopt; +} - std::getline(in,line); - //skip comments - while( line[0] == '%' ) { - std::getline(in, line); - } +[[nodiscard]] constexpr auto distribution_boundary(NodeID nodes, + int boundary, + int size) noexcept + -> NodeID { + auto const divisor = static_cast(size); + auto const chunk = nodes / divisor + (nodes % divisor != 0 ? 1 : 0); + if (chunk == 0) { + return 0; + } + auto const index = static_cast(boundary); + if (index > nodes / chunk) { + return nodes; + } + return std::min(nodes, index * chunk); +} + +struct graph_interval final { + NodeID first = 0; + NodeID next = 0; + + [[nodiscard]] constexpr auto size() const noexcept -> NodeID { + return next - first; + } + [[nodiscard]] constexpr auto inclusive_last() const noexcept -> NodeID { + return first == next ? first : next - 1; + } +}; + +[[nodiscard]] constexpr auto local_graph_interval(NodeID nodes, + int rank, + int size) noexcept + -> graph_interval { + return graph_interval{distribution_boundary(nodes, rank, size), + distribution_boundary(nodes, rank + 1, size)}; +} - int ew = 0; - std::stringstream ss(line); - ss >> nmbNodes; - ss >> nmbEdges; - ss >> ew; +[[nodiscard]] auto graph_distribution(NodeID nodes, int size) + -> std::vector { + auto result = std::vector(static_cast(size) + 1); + for (auto boundary = 0; boundary <= size; ++boundary) { + result[static_cast(boundary)] = + distribution_boundary(nodes, boundary, size); + } + return result; +} - if(ew != 0) { - in.close(); - if(peID == 0) std::cout << "graph is weighted --> using a different IO routine" << std::endl; - return readGraphWeightedMETIS_fixed(G, filename, peID, comm_size, communicator); - } - - // pe p reads the lines p*ceil(n/size) to (p+1)floor(n/size) lines of that file - ULONG from = peID * ceil(nmbNodes / (double)comm_size); - ULONG to = (peID+1) * ceil(nmbNodes / (double)comm_size) - 1; - to = std::min(to, nmbNodes-1); - - ULONG local_no_nodes = to - from + 1; - PRINT(std::cout << "peID " << peID << " from " << from << " to " << to << " amount " << local_no_nodes << std::endl;); - - std::vector< std::vector< NodeID > > local_edge_lists; - local_edge_lists.resize(local_no_nodes); - - ULONG counter = 0; - NodeID node_counter = 0; - EdgeID edge_counter = 0; - - char *oldstr, *newstr; - while( std::getline(in, line) ) { - if( counter > to ) { - break; - } - if (line[0] == '%') { // a comment in the file - continue; - } - - if( counter >= from ) { - oldstr = &line[0]; - newstr = 0; - - for (;;) { - NodeID target; - target = (NodeID) strtol(oldstr, &newstr, 10); - - if (target == 0) { - break; - } - - oldstr = newstr; - - local_edge_lists[node_counter].push_back(target); - edge_counter++; - - } - - node_counter++; - } - - counter++; - - if( in.eof() ) { - break; - } - } +struct graph_output_layout final { + NodeID global_nodes = 0; + EdgeID global_edges = 0; +}; + +[[nodiscard]] auto validated_graph_output_layout(parallel_graph_access& graph, + communicator_view communicator) + -> graph_output_layout { + auto const local = std::array{ + graph.number_of_global_nodes(), graph.number_of_global_edges(), + graph.get_from_range(), graph.number_of_local_nodes(), + graph.number_of_local_edges()}; + auto gathered = std::vector( + static_cast(communicator.size()) * local.size()); + mpi::check_or_abort( + MPI_Allgather(local.data(), static_cast(local.size()), MPI_UINT64_T, + gathered.data(), static_cast(local.size()), + MPI_UINT64_T, communicator.native_handle()), + communicator.native_handle(), "MPI_Allgather(graph output layout)"); + auto next_node = std::uint64_t{0}; + auto edge_sum = std::uint64_t{0}; + auto valid = true; + for (auto rank = 0; rank < communicator.size(); ++rank) { + auto const offset = static_cast(rank) * local.size(); + auto const rank_nodes = gathered[offset]; + auto const rank_edges = gathered[offset + 1]; + auto const rank_from = gathered[offset + 2]; + auto const rank_node_count = gathered[offset + 3]; + auto const rank_edge_count = gathered[offset + 4]; + valid = valid && rank_nodes == local[0] && rank_edges == local[1] && + rank_from == next_node && next_node <= local[0] && + rank_node_count <= local[0] - next_node && + rank_edge_count <= local[1] - edge_sum; + if (valid) { + next_node += rank_node_count; + edge_sum += rank_edge_count; + } + } + valid = valid && next_node == local[0] && edge_sum == local[1] && + local[1] % 2 == 0; + if (!valid) { + mpi::abort_on_programming_error( + communicator.native_handle(), + "graph output ranges and edges do not form the global graph order"); + } + return graph_output_layout{local[0], local[1]}; +} - MPI_Barrier(communicator); - - G.start_construction(local_no_nodes, 2*edge_counter, nmbNodes, 2*nmbEdges); - G.set_range(from, to); +[[nodiscard]] auto parse_unsigned_token(std::string_view token, + ULONG& value) noexcept -> bool { + if (token.empty()) { + return false; + } + auto const result = + std::from_chars(token.data(), token.data() + token.size(), value); + return result.ec == std::errc{} && result.ptr == token.data() + token.size(); +} - std::vector< NodeID > vertex_dist( comm_size+1, 0 ); - for( PEID peID = 0; peID <= comm_size; peID++) { - vertex_dist[peID] = peID * ceil(nmbNodes / (double)comm_size); // from positions - } - G.set_range_array(vertex_dist); - - for (NodeID i = 0; i < local_no_nodes; ++i) { - NodeID node = G.new_node(); - G.setNodeWeight(node, 1); - G.setNodeLabel(node, from+node); - G.setSecondPartitionIndex(node, 0); - - for( ULONG j = 0; j < local_edge_lists[i].size(); j++) { - NodeID target = local_edge_lists[i][j]-1; // -1 since there are no nodes with id 0 in the file - EdgeID e = G.new_edge(node, target); - G.setEdgeWeight(e, 1); - } +[[nodiscard]] auto parse_metis_header(std::string const& line, + NodeID& nodes, + EdgeID& undirected_edges, + int& format) -> bool { + auto input = std::istringstream{line}; + auto nodes_token = std::string{}; + auto edges_token = std::string{}; + if (!(input >> nodes_token >> edges_token) || + !parse_unsigned_token(nodes_token, nodes) || + !parse_unsigned_token(edges_token, undirected_edges)) { + return false; + } + auto format_token = std::string{}; + if (input >> format_token) { + auto const parsed = std::from_chars( + format_token.data(), format_token.data() + format_token.size(), format); + if (parsed.ec != std::errc{} || + parsed.ptr != format_token.data() + format_token.size()) { + return false; + } + } else { + format = 0; + } + return format == 0 || format == 1 || format == 10 || format == 11; +} + +struct metis_node final { + NodeWeight weight = 1; + std::vector targets; + std::vector edge_weights; +}; + +[[nodiscard]] auto parse_metis_node(std::string const& line, + int format, + NodeID global_nodes, + metis_node& node) -> bool { + auto const reads_edge_weights = format == 1 || format == 11; + auto const reads_node_weight = format == 10 || format == 11; + auto input = std::istringstream{line}; + auto token = std::string{}; + if (reads_node_weight) { + if (!(input >> token) || !parse_unsigned_token(token, node.weight)) { + return false; + } + } + while (input >> token) { + auto target = NodeID{0}; + if (!parse_unsigned_token(token, target) || target == 0 || + target > global_nodes) { + return false; + } + auto edge_weight = EdgeWeight{1}; + if (reads_edge_weights) { + if (!(input >> token) || !parse_unsigned_token(token, edge_weight)) { + return false; + } + } + node.targets.push_back(target - 1); + node.edge_weights.push_back(edge_weight); + } + return input.eof(); +} + +[[nodiscard]] auto collectively_sum_edges(EdgeID local_edges, + communicator_view communicator, + std::string_view diagnostic) + -> EdgeID { + auto counts = + std::vector(static_cast(communicator.size())); + mpi::check_or_abort( + MPI_Allgather(&local_edges, 1, MPI_UINT64_T, counts.data(), 1, + MPI_UINT64_T, communicator.native_handle()), + communicator.native_handle(), "MPI_Allgather(graph I/O edge counts)"); + auto total = EdgeID{0}; + for (auto count : counts) { + if (count > std::numeric_limits::max() - total) { + mpi::abort_on_capacity_failure(communicator.native_handle(), diagnostic, + "global edge sum is not representable"); + } + total += count; + } + return total; +} + +void validate_binary_offset_ranges(std::span local_offsets, + ULONG adjacency_base, + ULONG expected_end, + communicator_view communicator) noexcept { + auto const local = + std::array{local_offsets.front(), local_offsets.back()}; + auto gathered = std::vector( + static_cast(communicator.size()) * local.size()); + mpi::check_or_abort( + MPI_Allgather(local.data(), static_cast(local.size()), MPI_UINT64_T, + gathered.data(), static_cast(local.size()), + MPI_UINT64_T, communicator.native_handle()), + communicator.native_handle(), + "MPI_Allgather(binary graph offset ranges)"); + + auto valid = gathered.front() == adjacency_base; + for (auto rank = 1; valid && rank < communicator.size(); ++rank) { + auto const previous = static_cast(rank - 1) * local.size(); + auto const current = static_cast(rank) * local.size(); + valid = gathered[previous + 1] == gathered[current]; + } + valid = valid && gathered.back() == expected_end; + require_graph_io_success( + valid, communicator, "binary graph payload I/O failed", + "MPI_Allreduce(binary graph offset range validation)"); +} + +void construct_metis_graph(parallel_graph_access& graph, + NodeID global_nodes, + EdgeID global_edges, + graph_interval interval, + std::vector const& nodes, + std::vector& distribution) { + auto local_edges = EdgeID{0}; + for (auto const& node : nodes) { + local_edges += static_cast(node.targets.size()); + } + graph.start_construction(interval.size(), local_edges, global_nodes, + global_edges); + graph.set_range(interval.first, interval.inclusive_last()); + graph.set_range_array(distribution); + for (auto const& source_data : nodes) { + auto const source = graph.new_node(); + graph.setNodeWeight(source, source_data.weight); + graph.setNodeLabel(source, interval.first + source); + graph.setSecondPartitionIndex(source, 0); + for (std::size_t edge_index = 0; edge_index < source_data.targets.size(); + ++edge_index) { + auto const edge = graph.new_edge(source, source_data.targets[edge_index]); + graph.setEdgeWeight(edge, source_data.edge_weights[edge_index]); + } + } + graph.finish_construction(); +} + +[[nodiscard]] auto read_metis_graph(parallel_graph_access& graph, + std::string const& filename, + PEID supplied_rank, + PEID supplied_size, + MPI_Comm native_communicator) -> int { + auto const borrowed = communicator_view{native_communicator}; + mpi::require_live_intracommunicator( + borrowed, "METIS graph input requires a live intracommunicator"); + validate_graph_call(graph, supplied_rank, supplied_size, borrowed); + auto operation = mpi::communicator{borrowed}; + auto const communicator = operation.view(); + try { + require_graph_filename(filename, communicator, "METIS graph I/O failed"); + auto input = std::ifstream{filename}; + require_graph_io_success(static_cast(input), communicator, + "METIS graph I/O failed", + "MPI_Allreduce(METIS graph open status)"); + + auto header_line = std::string{}; + auto found_header = false; + while (std::getline(input, header_line)) { + auto content = std::string_view{header_line}; + while (!content.empty() && + (content.front() == ' ' || content.front() == '\t' || + content.front() == '\r')) { + content.remove_prefix(1); + } + if (content.empty() || content.front() == '%') { + continue; + } + found_header = true; + break; + } + + auto global_nodes = NodeID{0}; + auto undirected_edges = EdgeID{0}; + auto format = 0; + auto header_success = + found_header && + parse_metis_header(header_line, global_nodes, undirected_edges, format); + require_graph_io_success(header_success, communicator, + "METIS graph I/O failed", + "MPI_Allreduce(METIS graph header status)"); + + auto const local_header = std::array{ + global_nodes, undirected_edges, static_cast(format)}; + auto minimum_header = std::array{}; + auto maximum_header = std::array{}; + mpi::check_or_abort( + MPI_Allreduce(local_header.data(), minimum_header.data(), + static_cast(local_header.size()), MPI_UINT64_T, + MPI_MIN, communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(METIS graph header minimum)"); + mpi::check_or_abort( + MPI_Allreduce(local_header.data(), maximum_header.data(), + static_cast(local_header.size()), MPI_UINT64_T, + MPI_MAX, communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(METIS graph header maximum)"); + if (minimum_header != maximum_header) { + mpi::abort_on_backend_failure(communicator.native_handle(), + "METIS graph I/O failed"); + } + if (undirected_edges > std::numeric_limits::max() / 2) { + mpi::abort_on_capacity_failure( + communicator.native_handle(), "METIS graph input", + "directed edge count is not representable"); + } + auto const global_edges = undirected_edges * 2; + auto const interval = local_graph_interval( + global_nodes, communicator.rank(), communicator.size()); + require_graph_capacity(std::in_range(interval.size()), + communicator, "METIS graph input", + "local vertex count is not representable"); + auto local_nodes = + std::vector(static_cast(interval.size())); + + auto global_node = NodeID{0}; + auto payload_success = true; + auto line = std::string{}; + while (std::getline(input, line)) { + auto content = std::string_view{line}; + while (!content.empty() && + (content.front() == ' ' || content.front() == '\t' || + content.front() == '\r')) { + content.remove_prefix(1); + } + if (!content.empty() && content.front() == '%') { + continue; + } + if (global_node >= global_nodes) { + payload_success = false; + break; + } + if (global_node >= interval.first && global_node < interval.next) { + auto& node = + local_nodes[static_cast(global_node - interval.first)]; + payload_success = parse_metis_node(line, format, global_nodes, node); + if (!payload_success) { + break; } + } + ++global_node; + } + payload_success = + payload_success && !input.bad() && global_node == global_nodes; + require_graph_io_success(payload_success, communicator, + "METIS graph I/O failed", + "MPI_Allreduce(METIS graph payload status)"); + + auto local_edges = EdgeID{0}; + for (auto const& node : local_nodes) { + auto const count = static_cast(node.targets.size()); + if (count > std::numeric_limits::max() - local_edges) { + mpi::abort_on_capacity_failure(communicator.native_handle(), + "METIS graph input", + "local edge count is not representable"); + } + local_edges += count; + } + auto const observed_edges = + collectively_sum_edges(local_edges, communicator, "METIS graph input"); + if (observed_edges != global_edges) { + mpi::abort_on_backend_failure(communicator.native_handle(), + "METIS graph I/O failed"); + } + auto distribution = graph_distribution(global_nodes, communicator.size()); + construct_metis_graph(graph, global_nodes, global_edges, interval, + local_nodes, distribution); + return 0; + } catch (...) { + mpi::abort_on_exception(communicator.native_handle(), + "METIS graph input failed"); + } +} +} // namespace + +parallel_graph_io::parallel_graph_io() = default; + +parallel_graph_io::~parallel_graph_io() = default; + +int parallel_graph_io::readGraphWeighted(PPartitionConfig& config, + parallel_graph_access& graph, + std::string filename, + PEID rank, + PEID size, + MPI_Comm communicator) { + auto const view = communicator_view{communicator}; + mpi::require_live_intracommunicator( + view, "graph input requires a live intracommunicator"); + validate_graph_call(graph, rank, size, view); + if (hasEnding(filename, ".graph")) { + auto const binary_filename = filename + ".bgf"; + auto const local_binary = file_exists(binary_filename) ? 1 : 0; + auto minimum_binary = 0; + auto maximum_binary = 0; + mpi::check_or_abort(MPI_Allreduce(&local_binary, &minimum_binary, 1, + MPI_INT, MPI_MIN, communicator), + communicator, + "MPI_Allreduce(binary graph availability minimum)"); + mpi::check_or_abort(MPI_Allreduce(&local_binary, &maximum_binary, 1, + MPI_INT, MPI_MAX, communicator), + communicator, + "MPI_Allreduce(binary graph availability maximum)"); + if (minimum_binary != maximum_binary) { + mpi::abort_on_backend_failure( + communicator, "binary graph availability differs across ranks"); + } + if (minimum_binary != 0) { + return readGraphBinary(config, graph, binary_filename, rank, size, + communicator); + } + } + if (hasEnding(filename, ".bgf")) { + return readGraphBinary(config, graph, std::move(filename), rank, size, + communicator); + } + return readGraphWeightedFlexible(graph, std::move(filename), rank, size, + communicator); +} + +int parallel_graph_io::readGraphWeightedFlexible(parallel_graph_access& graph, + std::string filename, + PEID rank, + PEID size, + MPI_Comm communicator) { + return read_metis_graph(graph, filename, rank, size, communicator); +} + +//int parallel_graph_io::readGraphWeightedMETISFast(parallel_graph_access & G, +//std::string filename, +//PEID peID, PEID comm_size, MPI_Comm communicator) { +//std::string line; - G.finish_construction(); - MPI_Barrier(communicator); - - return 0; -} - -//int parallel_graph_io::readGraphWeightedMETISFast(parallel_graph_access & G, - //std::string filename, - //PEID peID, PEID comm_size, MPI_Comm communicator) { - //std::string line; - - //// open file for reading - //std::ifstream in(filename.c_str()); - //if (!in) { - //std::cerr << "Error opening " << filename << std::endl; - //return 1; - //} - - //NodeID nmbNodes; - //EdgeID nmbEdges; - - //std::getline(in,line); - ////skip comments - //while( line[0] == '%' ) { - //std::getline(in, line); - //} - - //int ew = 0; - //std::stringstream ss(line); - //ss >> nmbNodes; - //ss >> nmbEdges; - //ss >> ew; - - //// pe p reads the lines p*ceil(n/size) to (p+1)floor(n/size) lines of that file - //ULONG from = peID * ceil(nmbNodes / (double)comm_size); - //ULONG to = (peID+1) * ceil(nmbNodes / (double)comm_size) - 1; - //to = std::min(to, nmbNodes-1); - - //ULONG local_no_nodes = to - from + 1; - //std::cout << "peID " << peID << " from " << from << " to " << to << " amount " << local_no_nodes << std::endl; - - //std::vector< std::vector< NodeID > > local_edge_lists; - //local_edge_lists.resize(local_no_nodes); - - //ULONG counter = 0; - //NodeID node_counter = 0; - //EdgeID edge_counter = 0; - - //char *oldstr, *newstr; - //while( std::getline(in, line) ) { - //if( counter > to ) { - //break; - //} - //if (line[0] == '%') { // a comment in the file - //continue; - //} - - //if( counter >= from ) { - //oldstr = &line[0]; - //newstr = 0; - - //for (;;) { - //NodeID target; - //target = (NodeID) strtol(oldstr, &newstr, 10); - - //if (target == 0) { - //break; - //} - - //oldstr = newstr; - - //local_edge_lists[node_counter].push_back(target); - //edge_counter++; - - //} - - //node_counter++; - //} - - //counter++; - - //if( in.eof() ) { - //break; - //} - //} - - //MPI_Barrier(communicator); - - //G.start_construction(local_no_nodes, 2*edge_counter, nmbNodes, 2*nmbEdges); - //G.set_range(from, to); - - //for (NodeID i = 0; i < local_no_nodes; ++i) { - //NodeID node = G.new_node(); - //G.setNodeWeight(node, 1); - //G.setNodeLabel(node, from+node); - //G.setSecondPartitionIndex(node, 0); - - //for( ULONG j = 0; j < local_edge_lists[i].size(); j++) { - //NodeID target = local_edge_lists[i][j]-1; // -1 since there are no nodes with id 0 in the file - //EdgeID e = G.new_edge(node, target); - //G.setEdgeWeight(e, 1); - //} - //} - - //G.finish_construction(); - //MPI_Barrier(communicator); - //return 0; +//// open file for reading +//std::ifstream in(filename.c_str()); +//if (!in) { +//std::cerr << "Error opening " << filename << std::endl; +//return 1; //} -// we start with the simplest version of IO -// where each process reads the graph sequentially -// -//int parallel_graph_io::readGraphWeightedMETIS(parallel_graph_access & G, - //std::string filename, - //PEID peID, PEID comm_size, MPI_Comm communicator) { - //std::string line; - - //// open file for reading - //std::ifstream in(filename.c_str()); - //if (!in) { - //std::cerr << "Error opening " << filename << std::endl; - //return 1; - //} - - //NodeID nmbNodes; - //EdgeID nmbEdges; - - //std::getline(in,line); - ////skip comments - //while( line[0] == '%' ) { - //std::getline(in, line); - //} - - //int ew = 0; - //std::stringstream ss(line); - //ss >> nmbNodes; - //ss >> nmbEdges; - //ss >> ew; - - //if(ew == 1) { - //std::cout << "io of weighted graphs not supported yet" << std::endl; - //exit(0); - //} else if (ew == 11) { - //std::cout << "io of weighted graphs not supported yet" << std::endl; - //exit(0); - //} else if (ew == 10) { - //std::cout << "io of weighted graphs not supported yet" << std::endl; - //exit(0); - //} - - //// pe p reads the lines p*ceil(n/size) to (p+1)floor(n/size) lines of that file - //ULONG from = peID * ceil(nmbNodes / (double)comm_size); - //ULONG to = (peID+1) * ceil(nmbNodes / (double)comm_size) - 1; - //to = std::min(to, nmbNodes-1); - - //ULONG local_no_nodes = to - from + 1; - //std::cout << "peID " << peID << " from " << from << " to " << to << " amount " << local_no_nodes << std::endl; - - //std::vector< std::vector< NodeID > > local_edge_lists; - //local_edge_lists.resize(local_no_nodes); - - - ////std::getline(in, line); - //ULONG counter = 0; - //NodeID node_counter = 0; - //EdgeID edge_counter = 0; - - //while( std::getline(in, line) ) { - //if( counter > to ) { - //break; - //} - //if (line[0] == '%') { // a comment in the file - //continue; - //} - - //if( counter >= from ) { - //std::stringstream ss(line); - - //NodeID target; - //while( ss >> target ) { - //local_edge_lists[node_counter].push_back(target); - //edge_counter++; - //} - //node_counter++; - //} - - //counter++; - - //if( in.eof() ) { - //break; - //} - //} - - //MPI_Barrier(communicator); - - //G.start_construction(local_no_nodes, 2*edge_counter, nmbNodes, 2*nmbEdges); - //G.set_range(from, to); - - //for (NodeID i = 0; i < local_no_nodes; ++i) { - //NodeID node = G.new_node(); - //G.setNodeWeight(node, 1); - //G.setNodeLabel(node, from+node); - //G.setSecondPartitionIndex(node, 0); - - //for( ULONG j = 0; j < local_edge_lists[i].size(); j++) { - //NodeID target = local_edge_lists[i][j]-1; // -1 since there are no nodes with id 0 in the file - //EdgeID e = G.new_edge(node, target); - //G.setEdgeWeight(e, 1); - //} - //} - - //G.finish_construction(); - //return 0; + +//NodeID nmbNodes; +//EdgeID nmbEdges; + +//std::getline(in,line); +////skip comments +//while( line[0] == '%' ) { +//std::getline(in, line); //} -int parallel_graph_io::writeGraphExternallyBinary(std::string input_filename, std::string output_filename) { +//int ew = 0; +//std::stringstream ss(line); +//ss >> nmbNodes; +//ss >> nmbEdges; +//ss >> ew; - std::string line; +//// pe p reads the lines p*ceil(n/size) to (p+1)floor(n/size) lines of that file +//ULONG from = peID * ceil(nmbNodes / (double)comm_size); +//ULONG to = (peID+1) * ceil(nmbNodes / (double)comm_size) - 1; +//to = std::min(to, nmbNodes-1); - // open file for reading - std::ifstream in(input_filename.c_str()); - if (!in) { - std::cerr << "Error opening " << input_filename << std::endl; - return 1; - } +//ULONG local_no_nodes = to - from + 1; +//std::cout << "peID " << peID << " from " << from << " to " << to << " amount " << local_no_nodes << std::endl; - NodeID n; - EdgeID m; +//std::vector< std::vector< NodeID > > local_edge_lists; +//local_edge_lists.resize(local_no_nodes); - std::getline(in,line); - //skip comments - while( line[0] == '%' ) { - std::getline(in, line); - } +//ULONG counter = 0; +//NodeID node_counter = 0; +//EdgeID edge_counter = 0; - int ew = 0; - std::stringstream ss(line); - ss >> n; - ss >> m; - ss >> ew; - - m *= 2; - - std::ofstream outfile; - outfile.open(output_filename.c_str(), std::ios::binary | std::ios::out); - outfile.write((char*)(&fileTypeVersionNumber), sizeof( ULONG )); - outfile.write((char*)(&n), sizeof( ULONG )); - outfile.write((char*)(&m), sizeof( ULONG )); - - NodeID offset = (header_count + n + 1) * (sizeof(ULONG)); - - while( std::getline(in, line) ) { - if (line[0] == '%') { // a comment in the file - continue; - } - - std::stringstream ss(line); - - EdgeID edge_counter = 0; - NodeID target; - while( ss >> target ) { - edge_counter++; - } - outfile.write((char*)(&offset), sizeof( ULONG )); - offset += edge_counter*sizeof( ULONG ); - - if( in.eof() ) { - break; - } - } - outfile.write((char*)(&offset), sizeof( ULONG )); - in.close(); - - // second stream to actually write the edges - std::ifstream second_in(input_filename.c_str()); - std::getline(second_in,line); - //skip comments - while( line[0] == '%' ) { - std::getline(second_in, line); - } +//char *oldstr, *newstr; +//while( std::getline(in, line) ) { +//if( counter > to ) { +//break; +//} +//if (line[0] == '%') { // a comment in the file +//continue; +//} - while( std::getline(second_in, line) ) { - if (line[0] == '%') { // a comment in the file - continue; - } +//if( counter >= from ) { +//oldstr = &line[0]; +//newstr = 0; - std::stringstream ss(line); +//for (;;) { +//NodeID target; +//target = (NodeID) strtol(oldstr, &newstr, 10); - NodeID target; - while( ss >> target ) { - target -= 1; - outfile.write((char*)(&target), sizeof( ULONG )); - } +//if (target == 0) { +//break; +//} - if( second_in.eof() ) { - break; - } - } - second_in.close(); - - return 0; +//oldstr = newstr; -} +//local_edge_lists[node_counter].push_back(target); +//edge_counter++; + +//} -int parallel_graph_io::writeGraphSequentiallyBinary(complete_graph_access & G, std::string filename) { +//node_counter++; +//} - std::ofstream outfile; - outfile.open(filename.c_str(), std::ios::binary | std::ios::out); - PEID size; MPI_Comm_size( MPI_COMM_WORLD, &size); - - if( size > 1 ) { - std::cout << "currently only one process supported." << std::endl; - return 0; - } +//counter++; - std::cout << "Writing graph " << filename << std::endl; - printf("Writing graph with n = %lld, m = %lld\n", G.number_of_global_nodes(), G.number_of_global_edges()); - - //write version number - outfile.write((char*)(&fileTypeVersionNumber), sizeof( ULONG )); - - //write number of nodes etc - NodeID n = G.number_of_global_nodes(); - NodeID m = G.number_of_global_edges(); - - outfile.write((char*)(&n), sizeof( ULONG )); - outfile.write((char*)(&m), sizeof( ULONG )); - - NodeID * offset_array = new NodeID[n+1]; - ULONG pos = 0; - NodeID offset = (header_count + G.number_of_global_nodes() + 1) * (sizeof(ULONG)); - - forall_local_nodes(G, node) { - offset_array[pos++] = offset; - offset += G.getNodeDegree(node) * (sizeof(ULONG)); - } endfor - - offset_array[pos] = offset; - outfile.write((char*)(offset_array), (n+1)*sizeof(ULONG)); - delete[] offset_array; - - NodeID * edge_array = new NodeID[m]; - pos = 0; - - // now write the edges - forall_local_nodes(G, node) { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - edge_array[pos++] = target; - } endfor - } endfor - outfile.write((char*)(edge_array), (m)*sizeof(ULONG)); - outfile.close(); - delete[] edge_array; - - - return 0; -} - -int parallel_graph_io::readGraphBinary(PPartitionConfig & config, parallel_graph_access & G, - std::string filename, - PEID peID, PEID size, MPI_Comm communicator) { - - // read header - std::vector< ULONG > buffer(3, 0); - int success = 0; - if( peID == ROOT) { - std::cout << "Reading binary graph ..." << std::endl; - std::ifstream file; - file.open(filename.c_str(), std::ios::binary | std::ios::in); - if(file) { - success = 1; - file.read((char*)(&buffer[0]), 3*sizeof(ULONG)); - } - file.close(); - } +//if( in.eof() ) { +//break; +//} +//} - MPI_Bcast(&success, 1, MPI_INT, ROOT, communicator); +//MPI_Barrier(communicator); - if( !success ) { - if( peID == ROOT ) std::cout << "problem to open the file" << std::endl; - MPI_Finalize(); - exit(0); - } +//G.start_construction(local_no_nodes, 2*edge_counter, nmbNodes, 2*nmbEdges); +//G.set_range(from, to); - MPI_Bcast(&buffer[0], 3, MPI_LONG, ROOT, communicator); - ULONG version = buffer[0]; - NodeID n = buffer[1]; - NodeID m = buffer[2]; +//for (NodeID i = 0; i < local_no_nodes; ++i) { +//NodeID node = G.new_node(); +//G.setNodeWeight(node, 1); +//G.setNodeLabel(node, from+node); +//G.setSecondPartitionIndex(node, 0); - if(peID == ROOT) std::cout << "version: " << version << " n: "<< n << " m: " << m << std::endl; - if( version != fileTypeVersionNumber ) { - if(peID == ROOT) std::cout << "filetype version missmatch" << std::endl; - MPI_Finalize(); exit(0); - } +//for( ULONG j = 0; j < local_edge_lists[i].size(); j++) { +//NodeID target = local_edge_lists[i][j]-1; // -1 since there are no nodes with id 0 in the file +//EdgeID e = G.new_edge(node, target); +//G.setEdgeWeight(e, 1); +//} +//} - PEID window_size = std::min(config.binary_io_window_size, size); - PEID lowPE = 0; - PEID highPE = window_size; - - - while ( lowPE < size ) { - if( peID >= lowPE && peID < highPE ) { - std::ifstream file; - file.open(filename.c_str(), std::ios::binary | std::ios::in); - - ULONG from = peID * ceil(n / (double)size); - ULONG to = (peID +1) * ceil(n / (double)size) - 1; - to = std::min(to, n-1); - - ULONG local_no_nodes = to - from + 1; - std::cout << "peID " << peID << " from " << from << " to " << to << " amount " << local_no_nodes << std::endl; - - // read the offsets - ULONG start_pos = (header_count + from)*(sizeof(ULONG)); - NodeID* vertex_offsets = new NodeID[local_no_nodes+1]; // we also need the next vertex offset - file.seekg(start_pos); - file.read((char*)(vertex_offsets), (local_no_nodes+1)*sizeof(ULONG)); - - ULONG edge_start_pos = vertex_offsets[0]; - EdgeID num_reads = vertex_offsets[local_no_nodes]-vertex_offsets[0]; - EdgeID num_edges_to_read = num_reads/sizeof(ULONG); - EdgeID* edges = new EdgeID[num_edges_to_read]; // we also need the next vertex offset - file.seekg(edge_start_pos); - file.read((char*)(edges), (num_edges_to_read)*sizeof(ULONG)); - - G.start_construction(local_no_nodes, num_edges_to_read, n, m); - G.set_range(from, to); - - std::vector< NodeID > vertex_dist( size+1, 0 ); - for( PEID peID = 0; peID <= size; peID++) { - vertex_dist[peID] = peID * ceil(n / (double)size); // from positions - } - G.set_range_array(vertex_dist); - - ULONG pos = 0; - for (NodeID i = 0; i < local_no_nodes; ++i) { - NodeID node = G.new_node(); - G.setNodeWeight(node, 1); - G.setNodeLabel(node, from+node); - G.setSecondPartitionIndex(node, 0); - - NodeID degree = (vertex_offsets[i+1] - vertex_offsets[i]) / sizeof(ULONG); - for( ULONG j = 0; j < degree; j++, pos++) { - NodeID target = edges[pos]; - EdgeID e = G.new_edge(node, target); - G.setEdgeWeight(e, 1); - } - } - - G.finish_construction(); - - delete[] vertex_offsets; - delete[] edges; - file.close(); - } - lowPE += window_size; - highPE += window_size; - MPI_Barrier(communicator); - } - - return 0; -} - -int parallel_graph_io::writeGraphParallelSimple(parallel_graph_access & G, - std::string filename, MPI_Comm communicator) { - PEID rank, size; - MPI_Comm_rank( communicator, &rank); - MPI_Comm_size( communicator, &size); - if( rank == ROOT ) { - std::ofstream f(filename.c_str()); - f << G.number_of_global_nodes() << " " << G.number_of_global_edges()/2 << std::endl; - - forall_local_nodes(G, node) { - forall_out_edges(G, e, node) { - f << (G.getGlobalID(G.getEdgeTarget(e))+1) << " " ; - } endfor - f << "\n"; - } endfor - - f.close(); - } - - for( int i = 1; i < size; i++) { - MPI_Barrier(communicator); - - if( rank == i ) { - std::ofstream f; - f.open(filename.c_str(), std::ofstream::out | std::ofstream::app); - forall_local_nodes(G, node) { - forall_out_edges(G, e, node) { - f << (G.getGlobalID(G.getEdgeTarget(e))+1) << " " ; - } endfor - f << "\n"; - } endfor - f.close(); - } - } +//G.finish_construction(); +//MPI_Barrier(communicator); +//return 0; +//} +// we start with the simplest version of IO +// where each process reads the graph sequentially +// +//int parallel_graph_io::readGraphWeightedMETIS(parallel_graph_access & G, +//std::string filename, +//PEID peID, PEID comm_size, MPI_Comm communicator) { +//std::string line; + +//// open file for reading +//std::ifstream in(filename.c_str()); +//if (!in) { +//std::cerr << "Error opening " << filename << std::endl; +//return 1; +//} - MPI_Barrier(communicator); - - return 0; -} - -int parallel_graph_io::writeGraphWeightedParallelSimple(parallel_graph_access & G, - std::string filename, MPI_Comm communicator) { - PEID rank, size; - MPI_Comm_rank( communicator, &rank); - MPI_Comm_size( communicator, &size); - - if( rank == ROOT ) { - std::ofstream f(filename.c_str()); - f << G.number_of_global_nodes() << " " << G.number_of_global_edges()/2 << " 11" << std::endl; - - forall_local_nodes(G, node) { - f << G.getNodeWeight(node) ; - forall_out_edges(G, e, node) { - f << " " << (G.getGlobalID(G.getEdgeTarget(e))+1) << " " << G.getEdgeWeight(e) ; - } endfor - f << "\n"; - } endfor - - f.close(); - } - - for( PEID i = 1; i < size; i++) { - MPI_Barrier(communicator); - - if( rank == i ) { - std::ofstream f; - f.open(filename.c_str(), std::ofstream::out | std::ofstream::app); - forall_local_nodes(G, node) { - f << G.getNodeWeight(node) ; - forall_out_edges(G, e, node) { - f << " " << (G.getGlobalID(G.getEdgeTarget(e))+1) << " " << G.getEdgeWeight(e) ; - } endfor - f << "\n"; - } endfor - f.close(); - } - } +//NodeID nmbNodes; +//EdgeID nmbEdges; - MPI_Barrier(communicator); - - return 0; -} +//std::getline(in,line); +////skip comments +//while( line[0] == '%' ) { +//std::getline(in, line); +//} -int parallel_graph_io::writeGraphWeightedSequentially(complete_graph_access & G, std::string filename) { - std::ofstream f(filename.c_str()); - f << G.number_of_global_nodes() << " " << G.number_of_global_edges()/2 << " 11" << std::endl; +//int ew = 0; +//std::stringstream ss(line); +//ss >> nmbNodes; +//ss >> nmbEdges; +//ss >> ew; + +//if(ew == 1) { +//std::cout << "io of weighted graphs not supported yet" << std::endl; +//exit(0); +//} else if (ew == 11) { +//std::cout << "io of weighted graphs not supported yet" << std::endl; +//exit(0); +//} else if (ew == 10) { +//std::cout << "io of weighted graphs not supported yet" << std::endl; +//exit(0); +//} - forall_local_nodes(G, node) { - f << G.getNodeWeight(node) ; - forall_out_edges(G, e, node) { - f << " " << (G.getEdgeTarget(e)+1) << " " << G.getEdgeWeight(e) ; - } endfor - f << "\n"; - } endfor +//// pe p reads the lines p*ceil(n/size) to (p+1)floor(n/size) lines of that file +//ULONG from = peID * ceil(nmbNodes / (double)comm_size); +//ULONG to = (peID+1) * ceil(nmbNodes / (double)comm_size) - 1; +//to = std::min(to, nmbNodes-1); - f.close(); - return 0; -} +//ULONG local_no_nodes = to - from + 1; +//std::cout << "peID " << peID << " from " << from << " to " << to << " amount " << local_no_nodes << std::endl; -int parallel_graph_io::writeGraphSequentially(complete_graph_access & G, std::ofstream & f) { - f << G.number_of_global_nodes() << " " << G.number_of_global_edges()/2 << std::endl; +//std::vector< std::vector< NodeID > > local_edge_lists; +//local_edge_lists.resize(local_no_nodes); - forall_local_nodes(G, node) { - forall_out_edges(G, e, node) { - f << " " << (G.getEdgeTarget(e)+1) ; - } endfor - f << "\n"; - } endfor - return 0; -} -int parallel_graph_io::writeGraphSequentially(complete_graph_access & G, std::string filename) { - std::ofstream f(filename.c_str()); - writeGraphSequentially(G, f); - f.close(); - return 0; +////std::getline(in, line); +//ULONG counter = 0; +//NodeID node_counter = 0; +//EdgeID edge_counter = 0; -} +//while( std::getline(in, line) ) { +//if( counter > to ) { +//break; +//} +//if (line[0] == '%') { // a comment in the file +//continue; +//} -// we start with the simplest version of IO -// where each process reads the graph sequentially -// TODO write weighted code and fully parallel io code -int parallel_graph_io::readGraphWeightedMETIS_fixed(parallel_graph_access & G, - std::string filename, - PEID peID, PEID comm_size, MPI_Comm communicator) { - std::string line; - - // open file for reading - std::ifstream in(filename.c_str()); - if (!in) { - std::cerr << "Error opening " << filename << std::endl; - return 1; - } +//if( counter >= from ) { +//std::stringstream ss(line); + +//NodeID target; +//while( ss >> target ) { +//local_edge_lists[node_counter].push_back(target); +//edge_counter++; +//} +//node_counter++; +//} - NodeID nmbNodes; - EdgeID nmbEdges; +//counter++; - std::getline(in,line); - //skip comments - while( line[0] == '%' ) { - std::getline(in, line); - } +//if( in.eof() ) { +//break; +//} +//} - int ew = 0; - std::stringstream ss(line); - ss >> nmbNodes; - ss >> nmbEdges; - ss >> ew; - - // pe p reads the lines p*ceil(n/size) to (p+1)floor(n/size) lines of that file - ULONG from = peID * ceil(nmbNodes / (double)comm_size); - ULONG to = (peID+1) * ceil(nmbNodes / (double)comm_size) - 1; - to = std::min(to, nmbNodes-1); - - unsigned long local_no_nodes = to - from + 1; - std::cout << "peID " << peID << " from " << from << " to " << to << " amount " << local_no_nodes << std::endl; - - std::vector< std::vector< NodeID > > local_edge_lists; - local_edge_lists.resize(local_no_nodes); - - std::vector< std::vector< NodeID > > local_edge_weights; - local_edge_weights.resize(local_no_nodes); - - std::vector< NodeID > local_node_weights; - - bool read_ew = false; - bool read_nw = false; - - if(ew == 1) { - read_ew = true; - } else if (ew == 11) { - read_ew = true; - read_nw = true; - } else if (ew == 10) { - read_nw = true; +//MPI_Barrier(communicator); + +//G.start_construction(local_no_nodes, 2*edge_counter, nmbNodes, 2*nmbEdges); +//G.set_range(from, to); + +//for (NodeID i = 0; i < local_no_nodes; ++i) { +//NodeID node = G.new_node(); +//G.setNodeWeight(node, 1); +//G.setNodeLabel(node, from+node); +//G.setSecondPartitionIndex(node, 0); + +//for( ULONG j = 0; j < local_edge_lists[i].size(); j++) { +//NodeID target = local_edge_lists[i][j]-1; // -1 since there are no nodes with id 0 in the file +//EdgeID e = G.new_edge(node, target); +//G.setEdgeWeight(e, 1); +//} +//} + +//G.finish_construction(); +//return 0; +//} + +int parallel_graph_io::writeGraphExternallyBinary(std::string input_filename, std::string output_filename) { + + std::string line; + + // open file for reading + std::ifstream in(input_filename.c_str()); + if (!in) { + std::cerr << "Error opening " << input_filename << std::endl; + return 1; + } + + NodeID n; + EdgeID m; + + std::getline(in,line); + //skip comments + while( line[0] == '%' ) { + std::getline(in, line); + } + + int ew = 0; + std::stringstream ss(line); + ss >> n; + ss >> m; + ss >> ew; + + m *= 2; + + std::ofstream outfile; + outfile.open(output_filename.c_str(), std::ios::binary | std::ios::out); + outfile.write((char*)(&fileTypeVersionNumber), sizeof( ULONG )); + outfile.write((char*)(&n), sizeof( ULONG )); + outfile.write((char*)(&m), sizeof( ULONG )); + + NodeID offset = (header_count + n + 1) * (sizeof(ULONG)); + + while( std::getline(in, line) ) { + if (line[0] == '%') { // a comment in the file + continue; + } + + std::stringstream ss(line); + + EdgeID edge_counter = 0; + NodeID target; + while( ss >> target ) { + edge_counter++; + } + outfile.write((char*)(&offset), sizeof( ULONG )); + offset += edge_counter*sizeof( ULONG ); + + if( in.eof() ) { + break; + } + } + outfile.write((char*)(&offset), sizeof( ULONG )); + in.close(); + + // second stream to actually write the edges + std::ifstream second_in(input_filename.c_str()); + std::getline(second_in,line); + //skip comments + while( line[0] == '%' ) { + std::getline(second_in, line); + } + + while( std::getline(second_in, line) ) { + if (line[0] == '%') { // a comment in the file + continue; + } + + std::stringstream ss(line); + + NodeID target; + while( ss >> target ) { + target -= 1; + outfile.write((char*)(&target), sizeof( ULONG )); + } + + if( second_in.eof() ) { + break; + } + } + second_in.close(); + + return 0; + +} + +int parallel_graph_io::writeGraphSequentiallyBinary( + complete_graph_access& graph, + std::string filename) { + auto operation = + mpi::communicator{communicator_view{graph.getCommunicator()}}; + auto const communicator = operation.view(); + if (communicator.size() != 1) { + mpi::abort_on_programming_error( + communicator.native_handle(), + "sequential binary graph output requires exactly one rank"); + } + try { + auto const global_nodes = graph.number_of_global_nodes(); + auto const global_edges = graph.number_of_global_edges(); + auto const node_storage_valid = + std::in_range(global_nodes) && + global_nodes < std::numeric_limits::max(); + auto const edge_storage_valid = std::in_range(global_edges); + if (!node_storage_valid || !edge_storage_valid) { + mpi::abort_on_capacity_failure(communicator.native_handle(), + "sequential binary graph output", + "graph storage size is not representable"); + } + auto const adjacency_base = binary_adjacency_base(global_nodes); + if (!adjacency_base.has_value()) { + mpi::abort_on_capacity_failure( + communicator.native_handle(), "sequential binary graph output", + "binary graph byte layout is not representable"); + } + + auto offsets = + std::vector(static_cast(global_nodes) + 1); + auto edges = std::vector(static_cast(global_edges)); + auto offset = *adjacency_base; + auto edge_position = std::size_t{0}; + for (NodeID node = 0; node < graph.number_of_local_nodes(); ++node) { + offsets[static_cast(node)] = offset; + auto const degree_bytes = + checked_multiply(graph.getNodeDegree(node), ULONG{sizeof(ULONG)}); + if (!degree_bytes.has_value() || + !checked_add(offset, *degree_bytes).has_value()) { + mpi::abort_on_capacity_failure( + communicator.native_handle(), "sequential binary graph output", + "binary graph adjacency offset is not representable"); + } + offset += *degree_bytes; + for (EdgeID edge = graph.get_first_edge(node); + edge < graph.get_first_invalid_edge(node); ++edge) { + if (edge_position >= edges.size()) { + mpi::abort_on_programming_error( + communicator.native_handle(), + "sequential binary graph edge count is inconsistent"); } + edges[edge_position++] = graph.getEdgeTarget(edge); + } + } + offsets.back() = offset; + if (graph.number_of_local_nodes() != global_nodes || + edge_position != edges.size()) { + mpi::abort_on_programming_error( + communicator.native_handle(), + "sequential binary graph counts are inconsistent"); + } + + std::cout << "Writing graph " << filename << std::endl; + auto const header = + std::array{fileTypeVersionNumber, global_nodes, global_edges}; + auto descriptor = graph_file_descriptor{ + ::open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644)}; + auto write_success = + descriptor && + write_graph_exact(descriptor.get(), std::span{header}, 0); + auto const offsets_position = ULONG{sizeof(header)}; + write_success = + write_success && + write_graph_exact(descriptor.get(), std::span{offsets}, + offsets_position) && + write_graph_exact(descriptor.get(), std::span{edges}, + *adjacency_base); + auto const close_success = descriptor && descriptor.close(); + if (!write_success || !close_success) { + mpi::abort_on_backend_failure(communicator.native_handle(), + "sequential binary graph I/O failed"); + } + return 0; + } catch (...) { + mpi::abort_on_exception(communicator.native_handle(), + "sequential binary graph output failed"); + } +} - //std::getline(in, line); - unsigned long counter = 0; - NodeID node_counter = 0; - EdgeID edge_counter = 0; - - while( std::getline(in, line) ) { - if( counter > to ) { - break; - } - if (line[0] == '%') { // a comment in the file - continue; - } - - if( counter >= from ) { - std::stringstream ss(line); - - NodeWeight weight = 1; - if( read_nw ) { - ss >> weight; - } - local_node_weights.push_back(weight); - - NodeID target; - while( ss >> target ) { - EdgeWeight edge_weight = 1; - if( read_ew ) { - ss >> edge_weight; - } - - local_edge_weights[node_counter].push_back(edge_weight); - local_edge_lists[node_counter].push_back(target); - edge_counter++; - } - node_counter++; - } - - counter++; - - if( in.eof() ) { - break; - } +int parallel_graph_io::readGraphBinary(PPartitionConfig& config, + parallel_graph_access& graph, + std::string filename, + PEID supplied_rank, + PEID supplied_size, + MPI_Comm native_communicator) { + auto const borrowed = communicator_view{native_communicator}; + mpi::require_live_intracommunicator( + borrowed, "binary graph input requires a live intracommunicator"); + validate_graph_call(graph, supplied_rank, supplied_size, borrowed); + + auto buffer = std::array{}; + int success = 0; + if (borrowed.rank() == ROOT) { + std::cout << "Reading binary graph ..." << std::endl; + auto file = std::ifstream{filename, std::ios::binary | std::ios::in}; + if (file) { + file.read(reinterpret_cast(buffer.data()), + static_cast(sizeof(buffer))); + success = file ? 1 : -1; + } + } + + mpi::broadcast_fixed(success, ROOT, borrowed, + "MPI_Bcast(binary graph read status)"); + + if (success == 0) { + mpi::abort_on_backend_failure(native_communicator, + "unable to open binary graph file"); + } + if (success < 0) { + mpi::abort_on_backend_failure(native_communicator, + "unable to read binary graph header"); + } + + mpi::broadcast_fixed(std::span{buffer}, ROOT, borrowed, + "MPI_Bcast(binary graph header)"); + auto const version = buffer[0]; + auto const global_nodes = NodeID{buffer[1]}; + auto const global_edges = EdgeID{buffer[2]}; + + if (borrowed.rank() == ROOT) { + std::cout << "version: " << version << " n: " << global_nodes + << " m: " << global_edges << std::endl; + } + if (version != fileTypeVersionNumber) { + mpi::abort_on_backend_failure(native_communicator, + "unsupported binary graph version"); + } + + auto operation = mpi::communicator{borrowed}; + auto const communicator = operation.view(); + try { + require_graph_filename(filename, communicator, + "binary graph payload I/O failed"); + auto const window = + graph_window(config.binary_io_window_size, communicator); + auto const interval = local_graph_interval( + global_nodes, communicator.rank(), communicator.size()); + auto const local_size_is_representable = + std::in_range(interval.size()) && + interval.size() < std::numeric_limits::max(); + require_graph_capacity(local_size_is_representable, communicator, + "binary graph input", + "local vertex count is not representable"); + + auto const adjacency_base = binary_adjacency_base(global_nodes); + auto const adjacency_bytes = + checked_multiply(global_edges, ULONG{sizeof(ULONG)}); + auto const expected_end = + adjacency_base.has_value() && adjacency_bytes.has_value() + ? checked_add(*adjacency_base, *adjacency_bytes) + : std::nullopt; + auto const local_offset_word = checked_add(header_count, interval.first); + auto const local_offset_byte = + local_offset_word.has_value() + ? checked_multiply(*local_offset_word, ULONG{sizeof(ULONG)}) + : std::nullopt; + require_graph_capacity( + adjacency_base.has_value() && expected_end.has_value() && + local_offset_byte.has_value() && + *expected_end <= + static_cast(std::numeric_limits::max()), + communicator, "binary graph input", + "binary graph byte layout is not representable"); + + auto vertex_offsets = + std::vector(static_cast(interval.size()) + 1); + auto edges = std::vector{}; + auto local_edges = EdgeID{0}; + auto const rank = communicator.rank(); + auto const size = communicator.size(); + for (auto low = 0; low < size; low += window) { + auto const high = std::min(size, low + window); + auto const active = rank >= low && rank < high; + auto descriptor = graph_file_descriptor{ + active ? ::open(filename.c_str(), O_RDONLY) : -1}; + require_graph_io_success( + !active || descriptor, communicator, + "binary graph payload I/O failed", + "MPI_Allreduce(binary graph payload open status)"); + + auto payload_success = true; + if (active) { + payload_success = read_graph_exact(descriptor.get(), + std::span{vertex_offsets}, + *local_offset_byte); + if (payload_success) { + payload_success = + std::ranges::is_sorted(vertex_offsets) && + std::ranges::all_of(vertex_offsets, [&](auto offset) { + return offset >= *adjacency_base && offset <= *expected_end && + (offset - *adjacency_base) % sizeof(ULONG) == 0; + }); + } + if (payload_success) { + auto const bytes = vertex_offsets.back() - vertex_offsets.front(); + local_edges = bytes / sizeof(ULONG); + payload_success = std::in_range(local_edges); + } + if (payload_success) { + edges.resize(static_cast(local_edges)); + payload_success = + read_graph_exact(descriptor.get(), std::span{edges}, + vertex_offsets.front()); } + if (payload_success) { + payload_success = std::ranges::all_of( + edges, [&](auto target) { return target < global_nodes; }); + } + auto const close_success = descriptor.close(); + payload_success = payload_success && close_success; + } + require_graph_io_success( + payload_success, communicator, "binary graph payload I/O failed", + "MPI_Allreduce(binary graph payload read status)"); + } + + validate_binary_offset_ranges(vertex_offsets, *adjacency_base, + *expected_end, communicator); + + auto const observed_edges = + collectively_sum_edges(local_edges, communicator, "binary graph input"); + if (observed_edges != global_edges) { + mpi::abort_on_backend_failure(communicator.native_handle(), + "binary graph payload I/O failed"); + } + + graph.start_construction(interval.size(), local_edges, global_nodes, + global_edges); + graph.set_range(interval.first, interval.inclusive_last()); + auto distribution = graph_distribution(global_nodes, communicator.size()); + graph.set_range_array(distribution); + auto edge_position = std::size_t{0}; + for (NodeID local = 0; local < interval.size(); ++local) { + auto const source = graph.new_node(); + graph.setNodeWeight(source, 1); + graph.setNodeLabel(source, interval.first + source); + graph.setSecondPartitionIndex(source, 0); + auto const degree = static_cast( + (vertex_offsets[static_cast(local) + 1] - + vertex_offsets[static_cast(local)]) / + sizeof(ULONG)); + for (auto local_edge = std::size_t{0}; local_edge < degree; + ++local_edge, ++edge_position) { + auto const edge = graph.new_edge(source, edges[edge_position]); + graph.setEdgeWeight(edge, 1); + } + } + graph.finish_construction(); + return 0; + } catch (...) { + mpi::abort_on_exception(communicator.native_handle(), + "binary graph input failed"); + } +} - MPI_Barrier(communicator); - G.start_construction(local_no_nodes, 2*edge_counter, nmbNodes, 2*nmbEdges); - G.set_range(from, to); +int parallel_graph_io::writeGraphParallelSimple(parallel_graph_access& graph, + std::string filename, + MPI_Comm native_communicator) { + auto const borrowed = communicator_view{native_communicator}; + mpi::require_live_intracommunicator( + borrowed, "graph text output requires a live intracommunicator"); + validate_graph_call(graph, borrowed.rank(), borrowed.size(), borrowed); + auto operation = mpi::communicator{borrowed}; + auto const communicator = operation.view(); + try { + require_graph_filename(filename, communicator, + "graph text output I/O failed"); + auto const layout = validated_graph_output_layout(graph, communicator); + auto const rank = communicator.rank(); + for (auto writer = 0; writer < communicator.size(); ++writer) { + auto output = std::ofstream{}; + auto open_success = true; + if (rank == writer) { + auto const mode = writer == ROOT ? std::ios::out | std::ios::trunc + : std::ios::out | std::ios::app; + output.open(filename, mode); + open_success = static_cast(output); + } + require_graph_io_success(open_success, communicator, + "graph text output I/O failed", + "MPI_Allreduce(graph text output open status)"); + + auto write_success = true; + if (rank == writer) { + if (writer == ROOT) { + output << layout.global_nodes << ' ' << layout.global_edges / 2 + << '\n'; + } + for (NodeID node = 0; node < graph.number_of_local_nodes(); ++node) { + for (EdgeID edge = graph.get_first_edge(node); + edge < graph.get_first_invalid_edge(node); ++edge) { + output << graph.getGlobalID(graph.getEdgeTarget(edge)) + 1 << ' '; + } + output << '\n'; + } + output.close(); + write_success = static_cast(output); + } + require_graph_io_success(write_success, communicator, + "graph text output I/O failed", + "MPI_Allreduce(graph text output write status)"); + } + return 0; + } catch (...) { + mpi::abort_on_exception(communicator.native_handle(), + "graph text output failed"); + } +} - std::vector< NodeID > vertex_dist( comm_size+1, 0 ); - for( PEID peID = 0; peID <= comm_size; peID++) { - vertex_dist[peID] = peID * ceil(nmbNodes / (double)comm_size); // from positions +int parallel_graph_io::writeGraphWeightedParallelSimple( + parallel_graph_access& graph, + std::string filename, + MPI_Comm native_communicator) { + auto const borrowed = communicator_view{native_communicator}; + mpi::require_live_intracommunicator( + borrowed, "weighted graph output requires a live intracommunicator"); + validate_graph_call(graph, borrowed.rank(), borrowed.size(), borrowed); + auto operation = mpi::communicator{borrowed}; + auto const communicator = operation.view(); + try { + require_graph_filename(filename, communicator, + "weighted graph output I/O failed"); + auto const layout = validated_graph_output_layout(graph, communicator); + auto const rank = communicator.rank(); + for (auto writer = 0; writer < communicator.size(); ++writer) { + auto output = std::ofstream{}; + auto open_success = true; + if (rank == writer) { + auto const mode = writer == ROOT ? std::ios::out | std::ios::trunc + : std::ios::out | std::ios::app; + output.open(filename, mode); + open_success = static_cast(output); + } + require_graph_io_success( + open_success, communicator, "weighted graph output I/O failed", + "MPI_Allreduce(weighted graph output open status)"); + + auto write_success = true; + if (rank == writer) { + if (writer == ROOT) { + output << layout.global_nodes << ' ' << layout.global_edges / 2 + << " 11\n"; } - G.set_range_array(vertex_dist); - - for (NodeID i = 0; i < local_no_nodes; ++i) { - NodeID node = G.new_node(); - G.setNodeWeight(node, local_node_weights[i]); - G.setNodeLabel(node, from+node); - G.setSecondPartitionIndex(node, 0); - - - for( unsigned j = 0; j < local_edge_lists[i].size(); j++) { - NodeID target = local_edge_lists[i][j]-1; // -1 since there are no nodes with id 0 in the file - EdgeID e = G.new_edge(node, target); - G.setEdgeWeight(e, local_edge_weights[i][j]); - } + for (NodeID node = 0; node < graph.number_of_local_nodes(); ++node) { + output << graph.getNodeWeight(node); + for (EdgeID edge = graph.get_first_edge(node); + edge < graph.get_first_invalid_edge(node); ++edge) { + output << ' ' << graph.getGlobalID(graph.getEdgeTarget(edge)) + 1 + << ' ' << graph.getEdgeWeight(edge); + } + output << '\n'; } + output.close(); + write_success = static_cast(output); + } + require_graph_io_success( + write_success, communicator, "weighted graph output I/O failed", + "MPI_Allreduce(weighted graph output write status)"); + } + return 0; + } catch (...) { + mpi::abort_on_exception(communicator.native_handle(), + "weighted graph output failed"); + } +} + +int parallel_graph_io::writeGraphWeightedSequentially(complete_graph_access & G, std::string filename) { + std::ofstream f(filename.c_str()); + f << G.number_of_global_nodes() << " " << G.number_of_global_edges()/2 << " 11" << std::endl; + + forall_local_nodes(G, node) { + f << G.getNodeWeight(node) ; + forall_out_edges(G, e, node) { + f << " " << (G.getEdgeTarget(e)+1) << " " << G.getEdgeWeight(e) ; + } endfor + f << "\n"; + } endfor + + f.close(); + return 0; +} - G.finish_construction(); - MPI_Barrier(communicator); - return 0; +int parallel_graph_io::writeGraphSequentially(complete_graph_access & G, std::ofstream & f) { + f << G.number_of_global_nodes() << " " << G.number_of_global_edges()/2 << std::endl; + + forall_local_nodes(G, node) { + forall_out_edges(G, e, node) { + f << " " << (G.getEdgeTarget(e)+1) ; + } endfor + f << "\n"; + } endfor + return 0; +} +int parallel_graph_io::writeGraphSequentially(complete_graph_access & G, std::string filename) { + std::ofstream f(filename); + writeGraphSequentially(G, f); + f.close(); + return 0; } +// we start with the simplest version of IO +// where each process reads the graph sequentially +// TODO write weighted code and fully parallel io code +int parallel_graph_io::readGraphWeightedMETIS_fixed( + parallel_graph_access& graph, + std::string filename, + PEID rank, + PEID size, + MPI_Comm communicator) { + return read_metis_graph(graph, filename, rank, size, communicator); +} +} diff --git a/parallel/parallel_src/lib/io/parallel_graph_io.h b/parallel/parallel_src/lib/io/parallel_graph_io.h index 02d18e26..6a22b2c6 100644 --- a/parallel/parallel_src/lib/io/parallel_graph_io.h +++ b/parallel/parallel_src/lib/io/parallel_graph_io.h @@ -16,56 +16,57 @@ #include "partition_config.h" #define MAXLINE 50000000 +namespace parhip { class parallel_graph_io { - public: - parallel_graph_io(); - virtual ~parallel_graph_io(); +public: + parallel_graph_io(); + virtual ~parallel_graph_io(); - static int readGraphWeighted(PPartitionConfig & config, parallel_graph_access & G, - std::string filename, - PEID peID, - PEID comm_size, MPI_Comm communicator = MPI_COMM_WORLD); + static int readGraphWeighted(PPartitionConfig & config, parallel_graph_access & G, + std::string filename, + PEID peID, + PEID comm_size, MPI_Comm communicator = MPI_COMM_WORLD); - //static int readGraphWeightedMETIS(parallel_graph_access & G, - //std::string filename, - //PEID peID, - //PEID comm_size, MPI_Comm communicator = MPI_COMM_WORLD); + //static int readGraphWeightedMETIS(parallel_graph_access & G, + //std::string filename, + //PEID peID, + //PEID comm_size, MPI_Comm communicator = MPI_COMM_WORLD); - static int readGraphWeightedFlexible(parallel_graph_access & G, std::string filename, PEID peID, PEID comm_size, MPI_Comm communicator = MPI_COMM_WORLD); + static int readGraphWeightedFlexible(parallel_graph_access & G, std::string filename, PEID peID, PEID comm_size, MPI_Comm communicator = MPI_COMM_WORLD); - //static int readGraphWeightedMETISFast(parallel_graph_access & G, - //std::string filename, - //PEID peID, - //PEID comm_size, MPI_Comm communicator = MPI_COMM_WORLD); + //static int readGraphWeightedMETISFast(parallel_graph_access & G, + //std::string filename, + //PEID peID, + //PEID comm_size, MPI_Comm communicator = MPI_COMM_WORLD); - static int readGraphBinary(PPartitionConfig & config, parallel_graph_access & G, - std::string filename, - PEID peID, - PEID comm_size, MPI_Comm communicator = MPI_COMM_WORLD); + static int readGraphBinary(PPartitionConfig & config, parallel_graph_access & G, + std::string filename, + PEID peID, + PEID comm_size, MPI_Comm communicator = MPI_COMM_WORLD); - static int writeGraphParallelSimple(parallel_graph_access & G, - std::string filename, MPI_Comm communicator = MPI_COMM_WORLD); + static int writeGraphParallelSimple(parallel_graph_access & G, + std::string filename, MPI_Comm communicator = MPI_COMM_WORLD); - static int writeGraphWeightedParallelSimple(parallel_graph_access & G, - std::string filename, MPI_Comm communicator = MPI_COMM_WORLD); + static int writeGraphWeightedParallelSimple(parallel_graph_access & G, + std::string filename, MPI_Comm communicator = MPI_COMM_WORLD); - static int writeGraphWeightedSequentially(complete_graph_access & G, - std::string filename); + static int writeGraphWeightedSequentially(complete_graph_access & G, + std::string filename); - static int writeGraphSequentially(complete_graph_access & G, - std::string filename); + static int writeGraphSequentially(complete_graph_access & G, + std::string filename); - static int writeGraphSequentially(complete_graph_access & G, - std::ofstream & f); + static int writeGraphSequentially(complete_graph_access & G, + std::ofstream & f); - static int writeGraphSequentiallyBinary(complete_graph_access & G, std::string filename); + static int writeGraphSequentiallyBinary(complete_graph_access & G, std::string filename); - static int writeGraphExternallyBinary(std::string intput_filename, std::string output_filename); + static int writeGraphExternallyBinary(std::string intput_filename, std::string output_filename); - static int readGraphWeightedMETIS_fixed(parallel_graph_access & G, std::string filename, PEID peID, PEID comm_size, MPI_Comm communicator = MPI_COMM_WORLD); + static int readGraphWeightedMETIS_fixed(parallel_graph_access & G, std::string filename, PEID peID, PEID comm_size, MPI_Comm communicator = MPI_COMM_WORLD); }; - +} #endif /* end of include guard: PARALLEL_GRAPH_IO_8HHCKD13 */ diff --git a/parallel/parallel_src/lib/io/parallel_vector_io.cpp b/parallel/parallel_src/lib/io/parallel_vector_io.cpp index c7cf2b19..7ca31e0b 100644 --- a/parallel/parallel_src/lib/io/parallel_vector_io.cpp +++ b/parallel/parallel_src/lib/io/parallel_vector_io.cpp @@ -5,260 +5,562 @@ * Christian Schulz *****************************************************************************/ -#include #include +#include +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_failure.h" +#include "communication/mpi_fixed_broadcast.h" +#include "communication/mpi_handles.h" #include "parallel_vector_io.h" #include "tools/helpers.h" +namespace parhip { +namespace { +using mpi::communicator_view; + +class file_descriptor final { + public: + file_descriptor() noexcept = default; + explicit file_descriptor(int descriptor) noexcept : descriptor_(descriptor) {} + ~file_descriptor() noexcept { + if (descriptor_ >= 0) { + static_cast(::close(descriptor_)); + } + } + + file_descriptor(file_descriptor const&) = delete; + auto operator=(file_descriptor const&) -> file_descriptor& = delete; + file_descriptor(file_descriptor&& other) noexcept + : descriptor_(std::exchange(other.descriptor_, -1)) {} + auto operator=(file_descriptor&& other) noexcept -> file_descriptor& { + if (this != &other) { + if (descriptor_ >= 0) { + static_cast(::close(descriptor_)); + } + descriptor_ = std::exchange(other.descriptor_, -1); + } + return *this; + } + + [[nodiscard]] explicit operator bool() const noexcept { + return descriptor_ >= 0; + } + [[nodiscard]] auto get() const noexcept -> int { return descriptor_; } + [[nodiscard]] auto close() noexcept -> bool { + if (descriptor_ < 0) { + return false; + } + auto const descriptor = std::exchange(descriptor_, -1); + return ::close(descriptor) == 0; + } + + private: + int descriptor_ = -1; +}; -parallel_vector_io::parallel_vector_io() { - +[[nodiscard]] auto byte_offset(ULONG word) noexcept + -> std::optional { + constexpr auto width = std::uint64_t{sizeof(ULONG)}; + if (word > std::numeric_limits::max() / width) { + return std::nullopt; + } + return static_cast(word) * width; } -parallel_vector_io::~parallel_vector_io() { - +[[nodiscard]] auto byte_offset(ULONG prefix, ULONG index) noexcept + -> std::optional { + if (index > std::numeric_limits::max() - prefix) { + return std::nullopt; + } + return byte_offset(prefix + index); } -void parallel_vector_io::writePartitionBinaryParallelPosix(PPartitionConfig & config, - parallel_graph_access & G, - std::string filename) { - - PEID rank, size; - MPI_Comm_rank( MPI_COMM_WORLD, &rank); - MPI_Comm_size( MPI_COMM_WORLD, &size); - - if( rank == ROOT ) { - ULONG n = G.number_of_global_nodes(); - int output_fd = open(filename.c_str(), O_WRONLY | O_CREAT, 0644); - write(output_fd, (char*)(&fileTypeVersionNumberPartition), sizeof( ULONG )); - write(output_fd, (char*)(&n), sizeof( ULONG )); - close(output_fd); - } - - MPI_Barrier(MPI_COMM_WORLD); - PEID window_size = std::min(config.binary_io_window_size, size); - PEID lowPE = 0; - PEID highPE = window_size; - while ( lowPE < size ) { - if( rank >= lowPE && rank < highPE ) { - int output_fd = open(filename.c_str(), O_WRONLY, 0644); - - ULONG from = G.get_from_range(); - ULONG start_pos = (header_count_partition + from)*(sizeof(ULONG)); - lseek( output_fd, start_pos, SEEK_SET); - - std::vector< ULONG > partition_ids(G.number_of_local_nodes(),0); - forall_local_nodes(G, node) { - ULONG block = G.getNodeLabel(node); - partition_ids[node]= block; - } endfor - - write(output_fd, (char*)(&partition_ids[0]), G.number_of_local_nodes()*sizeof( ULONG )); - close(output_fd); - } - lowPE += window_size; - highPE += window_size; - MPI_Barrier(MPI_COMM_WORLD); - } - - MPI_Barrier(MPI_COMM_WORLD); +[[nodiscard]] auto read_exact(int descriptor, + std::span bytes, + std::uint64_t offset) noexcept -> bool { + constexpr auto maximum_offset = + static_cast(std::numeric_limits::max()); + constexpr auto maximum_transfer = + static_cast(std::numeric_limits::max()); + while (!bytes.empty()) { + if (offset > maximum_offset) { + return false; + } + auto const transfer = std::min(bytes.size(), maximum_transfer); + auto const received = + ::pread(descriptor, bytes.data(), transfer, static_cast(offset)); + if (received < 0 && errno == EINTR) { + continue; + } + if (received <= 0) { + return false; + } + auto const count = static_cast(received); + bytes = bytes.subspan(count); + offset += static_cast(count); + } + return true; } -void parallel_vector_io::writePartitionBinaryParallel(PPartitionConfig & config, - parallel_graph_access & G, - std::string filename) { - - PEID rank, size; - MPI_Comm_rank( MPI_COMM_WORLD, &rank); - MPI_Comm_size( MPI_COMM_WORLD, &size); - - if( rank == ROOT ) { - // ROOT writes head - ULONG n = G.number_of_global_nodes(); - std::ofstream outfile; - outfile.open(filename.c_str(), std::ios::binary | std::ios::out); - outfile.write((char*)(&fileTypeVersionNumberPartition), sizeof( ULONG )); - outfile.write((char*)(&n), sizeof( ULONG )); - outfile.close(); - - } - - MPI_Barrier(MPI_COMM_WORLD); - PEID window_size = 1; - PEID lowPE = 0; - PEID highPE = window_size; - while ( lowPE < size ) { - if( rank >= lowPE && rank < highPE ) { - std::ofstream file; - file.open(filename.c_str(), std::ios::binary | std::ios::out | std::ios::app); - - ULONG from = G.get_from_range(); - ULONG start_pos = (header_count_partition + from)*(sizeof(ULONG)); - file.seekp(start_pos); - - std::vector< ULONG > partition_ids(G.number_of_local_nodes(),0); - forall_local_nodes(G, node) { - ULONG block = G.getNodeLabel(node); - partition_ids[node]= block; - } endfor - file.write((char*)(&partition_ids[0]), G.number_of_local_nodes()*sizeof( ULONG )); - - file.close(); - } - lowPE += window_size; - highPE += window_size; - MPI_Barrier(MPI_COMM_WORLD); - } - - MPI_Barrier(MPI_COMM_WORLD); +[[nodiscard]] auto write_exact(int descriptor, + std::span bytes, + std::uint64_t offset) noexcept -> bool { + constexpr auto maximum_offset = + static_cast(std::numeric_limits::max()); + constexpr auto maximum_transfer = + static_cast(std::numeric_limits::max()); + while (!bytes.empty()) { + if (offset > maximum_offset) { + return false; + } + auto const transfer = std::min(bytes.size(), maximum_transfer); + auto const written = ::pwrite(descriptor, bytes.data(), transfer, + static_cast(offset)); + if (written < 0 && errno == EINTR) { + continue; + } + if (written <= 0) { + return false; + } + auto const count = static_cast(written); + bytes = bytes.subspan(count); + offset += static_cast(count); + } + return true; } -void parallel_vector_io::readPartitionBinaryParallel(PPartitionConfig & config, - parallel_graph_access & G, - std::string filename) { - - PEID rank, size; - MPI_Comm_rank( MPI_COMM_WORLD, &rank); - MPI_Comm_size( MPI_COMM_WORLD, &size); - - if( rank == ROOT ) { - // ROOT reades head - std::cout << "reading binary partition" << std::endl; - std::ifstream file; - file.open(filename.c_str(), std::ios::binary | std::ios::in); - std::vector< ULONG > buffer(2, 0); - if(file) { - file.read((char*)(&buffer[0]), 2*sizeof(ULONG)); - if( buffer[0] != fileTypeVersionNumberPartition ) { - std::cout << "filetype version mismatch " << buffer[0] << "!=" << fileTypeVersionNumberPartition << std::endl; - exit(0); - } - if( buffer[1] != G.number_of_global_nodes()) { - std::cout << "wrong number of nodes in partition file" << std::endl; - exit(0); - } - } - file.close(); - } - - PEID window_size = std::min(config.binary_io_window_size, size); - PEID lowPE = 0; - PEID highPE = window_size; - while ( lowPE < size ) { - if( rank >= lowPE && rank < highPE ) { - std::ifstream file; - file.open(filename.c_str(), std::ios::binary | std::ios::in); - - ULONG from = G.get_from_range(); - ULONG ids_to_read = G.number_of_local_nodes(); - ULONG start_pos = (header_count_partition + from)*(sizeof(ULONG)); - file.seekg(start_pos); - - std::vector< ULONG > partition_ids(ids_to_read, 0); - file.read((char*)(&partition_ids[0]), ids_to_read*sizeof( ULONG )); - file.close(); - forall_local_nodes(G, node) { - G.setNodeLabel(node, partition_ids[node]); - } endfor - - } - lowPE += window_size; - highPE += window_size; - MPI_Barrier(MPI_COMM_WORLD); - } - - MPI_Barrier(MPI_COMM_WORLD); - G.update_ghost_node_data_global(); - MPI_Barrier(MPI_COMM_WORLD); +template +[[nodiscard]] auto read_exact(int descriptor, + std::span values, + std::uint64_t offset) noexcept -> bool { + static_assert(std::is_trivially_copyable_v); + return read_exact(descriptor, std::as_writable_bytes(values), offset); } +template +[[nodiscard]] auto write_exact(int descriptor, + std::span values, + std::uint64_t offset) noexcept -> bool { + static_assert(std::is_trivially_copyable_v); + return write_exact(descriptor, std::as_bytes(values), offset); +} -void parallel_vector_io::writePartitionSimpleParallel(parallel_graph_access & G, - std::string filename) { - PEID rank, size; - MPI_Comm_rank( MPI_COMM_WORLD, &rank); - MPI_Comm_size( MPI_COMM_WORLD, &size); - - if( rank == ROOT ) { - std::ofstream f(filename.c_str()); - - forall_local_nodes(G, node) { - f << G.getNodeLabel(node) ; - f << std::endl; - } endfor - - f.close(); - } - - for( int i = 1; i < size; i++) { - MPI_Barrier(MPI_COMM_WORLD); - - if( rank == i ) { - std::ofstream f; - f.open(filename.c_str(), std::ofstream::out | std::ofstream::app); - forall_local_nodes(G, node) { - f << G.getNodeLabel(node) ; - f << std::endl; - } endfor - f.close(); - } - } - MPI_Barrier(MPI_COMM_WORLD); - +void require_collective_io_success( + bool local_success, + communicator_view communicator, + std::string_view diagnostic, + std::string_view agreement_context) noexcept { + auto const local = local_success ? 1 : 0; + auto global = 0; + mpi::check_or_abort(MPI_Allreduce(&local, &global, 1, MPI_INT, MPI_MIN, + communicator.native_handle()), + communicator.native_handle(), agreement_context); + if (global == 0) { + mpi::abort_on_backend_failure(communicator.native_handle(), diagnostic); + } } -void parallel_vector_io::readPartition(PPartitionConfig & config, parallel_graph_access & G, - std::string filename) { - std::string text_ending(".txtp"); - std::string bin_ending(".binp"); - if( hasEnding(filename, text_ending) ) { - return readPartitionSimpleParallel(G, filename); - } +void require_common_filename(std::string_view filename, + communicator_view communicator, + std::string_view diagnostic) { + auto size = std::uint64_t{0}; + if (communicator.rank() == ROOT) { + size = filename.size(); + } + mpi::broadcast_fixed(size, ROOT, communicator, + "MPI_Bcast(partition I/O filename size)"); + if (!std::in_range(size)) { + mpi::abort_on_capacity_failure(communicator.native_handle(), + "partition I/O filename", + "filename size is not representable"); + } + auto canonical = std::string(static_cast(size), '\0'); + if (communicator.rank() == ROOT) { + std::ranges::copy(filename, canonical.begin()); + } + mpi::broadcast_bounded(std::span{canonical}, ROOT, communicator, + "MPI_Bcast(partition I/O filename)"); + require_collective_io_success( + filename == canonical, communicator, diagnostic, + "MPI_Allreduce(partition I/O filename agreement)"); +} - if( hasEnding(filename, bin_ending) ) { - return readPartitionBinaryParallel(config, G, filename); - } +[[nodiscard]] auto effective_window(int configured, + communicator_view communicator) noexcept + -> int { + auto const local = std::max(1, std::min(configured, communicator.size())); + auto minimum = 0; + auto maximum = 0; + mpi::check_or_abort(MPI_Allreduce(&local, &minimum, 1, MPI_INT, MPI_MIN, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(partition I/O window minimum)"); + mpi::check_or_abort(MPI_Allreduce(&local, &maximum, 1, MPI_INT, MPI_MAX, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(partition I/O window maximum)"); + if (minimum != maximum) { + mpi::abort_on_programming_error( + communicator.native_handle(), + "partition I/O window differs across communicator"); + } + return minimum; } -void parallel_vector_io::readPartitionSimpleParallel(parallel_graph_access & G, - std::string filename) { - PEID rank, size; - MPI_Comm_rank( MPI_COMM_WORLD, &rank); - MPI_Comm_size( MPI_COMM_WORLD, &size); - - MPI_Barrier(MPI_COMM_WORLD); - if( rank == ROOT ) { std::cout << "reading text partition" << std::endl; } - - std::string line; - // open file for reading - std::ifstream in(filename.c_str()); - if (!in) { - std::cerr << "Error opening file" << filename << std::endl; - return; - } +struct partition_layout final { + ULONG global_nodes = 0; + ULONG from = 0; + ULONG local_nodes = 0; +}; - NodeID counter = 0; - NodeID from = G.get_from_range(); - NodeID to = G.get_to_range(); - - std::getline(in, line); - while( !in.eof() ) { - if( counter > to ) { - break; - } - - if( counter >= from ) { - PartitionID block_id = (PartitionID) atof(line.c_str()); - G.setNodeLabel(counter-from, block_id); - } - counter++; - std::getline(in, line); - } +[[nodiscard]] auto validated_layout(parallel_graph_access& graph, + communicator_view communicator) + -> partition_layout { + auto const local = std::array{ + graph.number_of_global_nodes(), graph.get_from_range(), + graph.number_of_local_nodes()}; + auto gathered = std::vector( + static_cast(communicator.size()) * local.size()); + mpi::check_or_abort( + MPI_Allgather(local.data(), static_cast(local.size()), MPI_UINT64_T, + gathered.data(), static_cast(local.size()), + MPI_UINT64_T, communicator.native_handle()), + communicator.native_handle(), + "MPI_Allgather(partition I/O graph layout)"); + + auto const global_nodes = local[0]; + auto next = std::uint64_t{0}; + auto valid = true; + for (auto rank = 0; rank < communicator.size(); ++rank) { + auto const offset = static_cast(rank) * local.size(); + auto const rank_global = gathered[offset]; + auto const rank_from = gathered[offset + 1]; + auto const rank_count = gathered[offset + 2]; + valid = valid && rank_global == global_nodes && rank_from == next && + next <= global_nodes && rank_count <= global_nodes - next; + if (valid) { + next += rank_count; + } + } + valid = valid && next == global_nodes; + if (!valid) { + mpi::abort_on_programming_error( + communicator.native_handle(), + "partition I/O graph ranges do not form the global vertex order"); + } + return partition_layout{local[0], local[1], local[2]}; +} + +[[nodiscard]] auto parse_partition_id(std::string_view line, + ULONG& value) noexcept -> bool { + auto const whitespace = [](char character) { + return character == ' ' || character == '\t' || character == '\r'; + }; + while (!line.empty() && whitespace(line.front())) { + line.remove_prefix(1); + } + while (!line.empty() && whitespace(line.back())) { + line.remove_suffix(1); + } + if (line.empty()) { + return false; + } + auto const result = + std::from_chars(line.data(), line.data() + line.size(), value); + return result.ec == std::errc{} && result.ptr == line.data() + line.size(); +} +} // namespace + +parallel_vector_io::parallel_vector_io() = default; - MPI_Barrier(MPI_COMM_WORLD); - G.update_ghost_node_data_global(); - MPI_Barrier(MPI_COMM_WORLD); - +parallel_vector_io::~parallel_vector_io() = default; + +void parallel_vector_io::writePartitionBinaryParallelPosix( + PPartitionConfig& config, + parallel_graph_access& graph, + std::string filename) { + auto operation = + mpi::communicator{communicator_view{graph.getCommunicator()}}; + auto const communicator = operation.view(); + try { + auto const rank = communicator.rank(); + auto const size = communicator.size(); + require_common_filename(filename, communicator, + "partition binary payload I/O failed"); + auto const layout = validated_layout(graph, communicator); + auto const window = + effective_window(config.binary_io_window_size, communicator); + + auto header_success = true; + if (rank == ROOT) { + auto const header = std::array{ + fileTypeVersionNumberPartition, layout.global_nodes}; + auto descriptor = file_descriptor{ + ::open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644)}; + auto const write_success = + descriptor && + write_exact(descriptor.get(), std::span{header}, 0); + auto const close_success = descriptor && descriptor.close(); + header_success = write_success && close_success; + } + require_collective_io_success( + header_success, communicator, "partition binary header I/O failed", + "MPI_Allreduce(partition binary header I/O status)"); + + auto labels = + std::vector(static_cast(layout.local_nodes)); + for (NodeID node = 0; node < graph.number_of_local_nodes(); ++node) { + labels[static_cast(node)] = graph.getNodeLabel(node); + } + auto const start = byte_offset(header_count_partition, layout.from); + if (!start.has_value()) { + mpi::abort_on_capacity_failure( + communicator.native_handle(), "partition binary output", + "partition byte offset is not representable"); + } + + for (auto low = 0; low < size; low += window) { + auto const high = std::min(size, low + window); + auto const active = rank >= low && rank < high; + auto descriptor = + file_descriptor{active ? ::open(filename.c_str(), O_WRONLY) : -1}; + require_collective_io_success( + !active || descriptor, communicator, + "partition binary payload I/O failed", + "MPI_Allreduce(partition binary payload open status)"); + + auto payload_success = true; + if (active) { + auto const write_success = write_exact( + descriptor.get(), std::span{labels}, *start); + auto const close_success = descriptor.close(); + payload_success = write_success && close_success; + } + require_collective_io_success( + payload_success, communicator, "partition binary payload I/O failed", + "MPI_Allreduce(partition binary payload write status)"); + } + } catch (...) { + mpi::abort_on_exception(communicator.native_handle(), + "partition binary output failed"); + } +} + +void parallel_vector_io::writePartitionBinaryParallel( + PPartitionConfig& config, + parallel_graph_access& graph, + std::string filename) { + writePartitionBinaryParallelPosix(config, graph, std::move(filename)); +} + +void parallel_vector_io::readPartitionBinaryParallel( + PPartitionConfig& config, + parallel_graph_access& graph, + std::string filename) { + auto operation = + mpi::communicator{communicator_view{graph.getCommunicator()}}; + auto const communicator = operation.view(); + try { + auto const rank = communicator.rank(); + auto const size = communicator.size(); + require_common_filename(filename, communicator, + "partition binary payload I/O failed"); + auto const layout = validated_layout(graph, communicator); + auto const window = + effective_window(config.binary_io_window_size, communicator); + if (rank == ROOT) { + std::cout << "reading binary partition" << std::endl; + } + + auto header = std::array{}; + auto header_success = true; + if (rank == ROOT) { + auto descriptor = file_descriptor{::open(filename.c_str(), O_RDONLY)}; + auto const read_success = + descriptor && + read_exact(descriptor.get(), std::span{header}, 0); + auto const close_success = descriptor && descriptor.close(); + header_success = read_success && close_success; + } + require_collective_io_success( + header_success, communicator, "partition binary header I/O failed", + "MPI_Allreduce(partition binary header I/O status)"); + mpi::broadcast_fixed(std::span{header}, ROOT, communicator, + "MPI_Bcast(partition binary header)"); + if (header[0] != fileTypeVersionNumberPartition || + header[1] != layout.global_nodes) { + mpi::abort_on_backend_failure(communicator.native_handle(), + "partition binary header is incompatible"); + } + + auto labels = + std::vector(static_cast(layout.local_nodes)); + auto const start = byte_offset(header_count_partition, layout.from); + if (!start.has_value()) { + mpi::abort_on_capacity_failure( + communicator.native_handle(), "partition binary input", + "partition byte offset is not representable"); + } + + for (auto low = 0; low < size; low += window) { + auto const high = std::min(size, low + window); + auto const active = rank >= low && rank < high; + auto descriptor = + file_descriptor{active ? ::open(filename.c_str(), O_RDONLY) : -1}; + require_collective_io_success( + !active || descriptor, communicator, + "partition binary payload I/O failed", + "MPI_Allreduce(partition binary payload open status)"); + + auto payload_success = true; + if (active) { + auto const read_success = + read_exact(descriptor.get(), std::span{labels}, *start); + auto const close_success = descriptor.close(); + payload_success = read_success && close_success; + } + require_collective_io_success( + payload_success, communicator, "partition binary payload I/O failed", + "MPI_Allreduce(partition binary payload read status)"); + } + + for (NodeID node = 0; node < graph.number_of_local_nodes(); ++node) { + graph.setNodeLabel(node, labels[static_cast(node)]); + } + graph.update_ghost_node_data_global(); + } catch (...) { + mpi::abort_on_exception(communicator.native_handle(), + "partition binary input failed"); + } } +void parallel_vector_io::writePartitionSimpleParallel( + parallel_graph_access& graph, + std::string filename) { + auto operation = + mpi::communicator{communicator_view{graph.getCommunicator()}}; + auto const communicator = operation.view(); + try { + auto const rank = communicator.rank(); + auto const size = communicator.size(); + require_common_filename(filename, communicator, + "partition text output I/O failed"); + static_cast(validated_layout(graph, communicator)); + + for (auto writer = 0; writer < size; ++writer) { + auto output = std::ofstream{}; + auto open_success = true; + if (rank == writer) { + auto const mode = writer == ROOT ? std::ios::out | std::ios::trunc + : std::ios::out | std::ios::app; + output.open(filename, mode); + open_success = static_cast(output); + } + require_collective_io_success( + open_success, communicator, "partition text output I/O failed", + "MPI_Allreduce(partition text output open status)"); + + auto write_success = true; + if (rank == writer) { + for (NodeID node = 0; node < graph.number_of_local_nodes(); ++node) { + output << graph.getNodeLabel(node) << '\n'; + } + output.close(); + write_success = static_cast(output); + } + require_collective_io_success( + write_success, communicator, "partition text output I/O failed", + "MPI_Allreduce(partition text output write status)"); + } + } catch (...) { + mpi::abort_on_exception(communicator.native_handle(), + "partition text output failed"); + } +} +void parallel_vector_io::readPartition(PPartitionConfig& config, + parallel_graph_access& graph, + std::string filename) { + if (hasEnding(filename, ".txtp")) { + return readPartitionSimpleParallel(graph, std::move(filename)); + } + if (hasEnding(filename, ".binp")) { + return readPartitionBinaryParallel(config, graph, std::move(filename)); + } +} + +void parallel_vector_io::readPartitionSimpleParallel( + parallel_graph_access& graph, + std::string filename) { + auto operation = + mpi::communicator{communicator_view{graph.getCommunicator()}}; + auto const communicator = operation.view(); + try { + auto const rank = communicator.rank(); + require_common_filename(filename, communicator, + "partition text input I/O failed"); + auto const layout = validated_layout(graph, communicator); + if (rank == ROOT) { + std::cout << "reading text partition" << std::endl; + } + + auto input = std::ifstream{filename}; + require_collective_io_success( + static_cast(input), communicator, + "partition text input I/O failed", + "MPI_Allreduce(partition text input open status)"); + + auto labels = + std::vector(static_cast(layout.local_nodes)); + auto line = std::string{}; + auto global = ULONG{0}; + auto parse_success = true; + while (std::getline(input, line)) { + auto content = std::string_view{line}; + while (!content.empty() && + (content.front() == ' ' || content.front() == '\t' || + content.front() == '\r')) { + content.remove_prefix(1); + } + if (!content.empty() && content.front() == '%') { + continue; + } + auto value = ULONG{0}; + if (global >= layout.global_nodes || + !parse_partition_id(content, value)) { + parse_success = false; + break; + } + if (global >= layout.from && global - layout.from < layout.local_nodes) { + labels[static_cast(global - layout.from)] = value; + } + ++global; + } + parse_success = + parse_success && !input.bad() && global == layout.global_nodes; + require_collective_io_success( + parse_success, communicator, "partition text input I/O failed", + "MPI_Allreduce(partition text input parse status)"); + + for (NodeID node = 0; node < graph.number_of_local_nodes(); ++node) { + graph.setNodeLabel(node, labels[static_cast(node)]); + } + graph.update_ghost_node_data_global(); + } catch (...) { + mpi::abort_on_exception(communicator.native_handle(), + "partition text input failed"); + } +} +} diff --git a/parallel/parallel_src/lib/io/parallel_vector_io.h b/parallel/parallel_src/lib/io/parallel_vector_io.h index 9b51c0f7..765bb4a0 100644 --- a/parallel/parallel_src/lib/io/parallel_vector_io.h +++ b/parallel/parallel_src/lib/io/parallel_vector_io.h @@ -8,17 +8,18 @@ #ifndef PARALLEL_VECTOR_IO_BZVNZ570A #define PARALLEL_VECTOR_IO_BZVNZ570A +#include +#include #include #include #include #include -#include -#include +#include #include #include "parallel_graph_io.h" #include "partition_config.h" - +namespace parhip { const ULONG fileTypeVersionNumberPartition = 1; const ULONG header_count_partition = 2; @@ -68,18 +69,19 @@ void parallel_vector_io::readVectorSequentially(std::vector & vec, s } ULONG pos = 0; - std::getline(in, line); - while( !in.eof() ) { - if (line[0] == '%') { //Comment - continue; - } - - vectortype value = (vectortype) atof(line.c_str()); - vec[pos++] = value; - std::getline(in, line); + while (pos < vec.size() && std::getline(in, line)) { + if (line.empty() || line.front() == '%') { + continue; + } + + auto parser = std::istringstream{line}; + auto value = vectortype{}; + if (parser >> value) { + vec[static_cast(pos++)] = value; + } } in.close(); } - +} #endif /* end of include guard: PARALLEL_VECTOR_IO_BZVNZ570 */ diff --git a/parallel/parallel_src/lib/parallel_contraction_projection/parallel_block_down_propagation.cpp b/parallel/parallel_src/lib/parallel_contraction_projection/parallel_block_down_propagation.cpp index e45b1358..7cb995cd 100644 --- a/parallel/parallel_src/lib/parallel_contraction_projection/parallel_block_down_propagation.cpp +++ b/parallel/parallel_src/lib/parallel_contraction_projection/parallel_block_down_propagation.cpp @@ -7,164 +7,388 @@ #include "parallel_block_down_propagation.h" -parallel_block_down_propagation::parallel_block_down_propagation() { - -} - -parallel_block_down_propagation::~parallel_block_down_propagation() { - -} - -void parallel_block_down_propagation::propagate_block_down( MPI_Comm communicator, PPartitionConfig & config, - parallel_graph_access & G, - parallel_graph_access & Q) { - +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include - std::unordered_map< NodeID, NodeID > coarse_block_ids; +#include "communication/contiguous_owner_layout.h" +#include "communication/ghost_exchange_plan.h" +#include "communication/mpi_adapter.h" +#include "communication/mpi_trace.h" - forall_local_nodes(G, node) { - NodeID cur_cnode = G.getCNode( node ); - coarse_block_ids[cur_cnode] = G.getSecondPartitionIndex( node ); - } endfor +namespace parhip { +void parallel_block_down_propagation::propagate_block_down( + MPI_Comm communicator, + PPartitionConfig& config, + parallel_graph_access& G, + parallel_graph_access& Q) { + auto const graph_communicator = mpi::communicator_view{Q.getCommunicator()}; + auto const rank = graph_communicator.rank(); + auto const size = graph_communicator.size(); + auto const rank_index = static_cast(rank); - PEID rank, size; - MPI_Comm_rank( communicator, &rank); - MPI_Comm_size( communicator, &size); - - NodeID divisor = ceil( Q.number_of_global_nodes()/(double)size); + auto communicators_are_compatible = + communicator != MPI_COMM_NULL && G.getCommunicator() != MPI_COMM_NULL; + if (communicators_are_compatible) { + auto graph_comparison = int{MPI_UNEQUAL}; + auto quotient_comparison = int{MPI_UNEQUAL}; + mpi::check_or_abort( + MPI_Comm_compare(communicator, G.getCommunicator(), &graph_comparison), + Q.getCommunicator(), "MPI_Comm_compare(block-down finer graph)"); + mpi::check_or_abort(MPI_Comm_compare(communicator, Q.getCommunicator(), + "ient_comparison), + Q.getCommunicator(), + "MPI_Comm_compare(block-down quotient graph)"); + communicators_are_compatible = + (graph_comparison == MPI_IDENT || graph_comparison == MPI_CONGRUENT) && + (quotient_comparison == MPI_IDENT || + quotient_comparison == MPI_CONGRUENT); + } + mpi::validate_collectively(communicators_are_compatible, graph_communicator, + "block-down communicator validation failed"); - m_messages.resize(size); + auto const number_of_blocks = mpi::agree_collectively( + config.k, graph_communicator, "block-down block-count agreement failed"); + mpi::validate_collectively(number_of_blocks > PartitionID{0}, + graph_communicator, + "block-down requires a positive block count"); + auto const number_of_coarse_nodes = + mpi::agree_collectively(Q.number_of_global_nodes(), graph_communicator, + "block-down coarse-node count agreement failed"); + auto const ownership = mpi::contiguous_owner_layout{ + number_of_coarse_nodes, static_cast(size)}; + auto const expected_from = ownership.begin(rank_index); + auto const expected_end = ownership.end(rank_index); + auto const expected_local_nodes = expected_end - expected_from; + auto const expected_to = + expected_local_nodes == 0 ? expected_from : expected_end - NodeID{1}; - //now distribute the block idw - //pack messages - for( auto it = coarse_block_ids.begin(); it != coarse_block_ids.end(); it++) { - NodeID node = it->first; - NodeID block = it->second; - PEID peID = node / divisor; + auto ownership_metadata_is_valid = + Q.number_of_local_nodes() == expected_local_nodes && + Q.get_from_range() == expected_from && Q.get_to_range() == expected_to && + std::in_range(Q.number_of_local_nodes()) && + std::in_range(Q.number_of_ghost_nodes()) && + Q.number_of_local_nodes() < std::numeric_limits::max() && + Q.number_of_ghost_nodes() <= std::numeric_limits::max() - + (Q.number_of_local_nodes() + NodeID{1}); + auto const& range_array = Q.get_range_array(); + ownership_metadata_is_valid = + ownership_metadata_is_valid && + range_array.size() == static_cast(size) + std::size_t{1}; + auto const range_limit = std::min( + range_array.size(), static_cast(size) + std::size_t{1}); + for (auto boundary = std::size_t{0}; boundary < range_limit; ++boundary) { + ownership_metadata_is_valid = + ownership_metadata_is_valid && + range_array[boundary] == ownership.boundary(boundary); + } + mpi::validate_collectively( + ownership_metadata_is_valid, graph_communicator, + "block-down quotient ownership metadata validation failed"); - m_messages[ peID ].push_back( node ); - m_messages[ peID ].push_back( block ); - } + auto local_updates = std::vector{}; + auto local_updates_are_valid = + std::in_range(G.number_of_local_nodes()); + try { + if (local_updates_are_valid) { + local_updates.reserve( + static_cast(G.number_of_local_nodes())); + } + for (auto node = NodeID{0}; node < G.number_of_local_nodes(); ++node) { + auto const coarse_global_id = G.getCNode(node); + auto const raw_block = G.getSecondPartitionIndex(node); + auto const block_is_representable = std::in_range(raw_block); + auto const block = block_is_representable + ? static_cast(raw_block) + : PartitionID{0}; + auto const local_global_id = G.getGlobalID(node); + local_updates_are_valid = local_updates_are_valid && + ownership.owner(coarse_global_id).has_value() && + block_is_representable && + block < number_of_blocks && + G.find_local_id(local_global_id) == node; + local_updates.push_back({coarse_global_id, block}); + } + std::ranges::stable_sort(local_updates, {}, [](auto const& update) { + return std::tie(update.coarse_global_id, update.block); + }); + for (auto index = std::size_t{1}; index < local_updates.size(); ++index) { + auto const& previous = local_updates[index - std::size_t{1}]; + auto const& current = local_updates[index]; + local_updates_are_valid = + local_updates_are_valid && + (previous.coarse_global_id != current.coarse_global_id || + previous.block == current.block); + } + } catch (...) { + mpi::abort_on_exception(Q.getCommunicator(), + "block-down local update staging"); + } + mpi::validate_collectively(local_updates_are_valid, graph_communicator, + "block-down local update validation failed"); - for( PEID peID = 0; peID < size; peID++) { - if( peID != rank ) { - if( m_messages[peID].size() == 0 ){ - m_messages[peID].push_back(std::numeric_limits::max()); - } + auto const& plan = Q.ghost_plan(); + auto dense_sends = mpi::segmented_buffer{}; + try { + auto updates_by_destination = + std::vector>( + static_cast(size)); + for (auto const& update : local_updates) { + auto const destination = ownership.owner(update.coarse_global_id); + if (!destination.has_value()) { + mpi::abort_on_programming_error( + plan.topology().native_handle(), + "validated block-down update has no owner"); + } + updates_by_destination[*destination].push_back(update); + } + dense_sends = + mpi::segmented_buffer::from_segments( + updates_by_destination); + } catch (...) { + mpi::abort_on_exception(plan.topology().native_handle(), + "block-down dense send staging"); + } - MPI_Request rq; - MPI_Isend( &m_messages[peID][0], - m_messages[peID].size(), - MPI_UNSIGNED_LONG_LONG, - peID, peID+10*size, communicator, &rq ); - } + auto dense_received = mpi::all_to_all_v(std::move(dense_sends), + mpi::communicator_view{communicator}); + auto owned_blocks = std::vector{}; + auto owned_assigned = std::vector{}; + auto dense_received_is_valid = + dense_received.segment_count() == static_cast(size); + try { + auto const local_count = static_cast(expected_local_nodes); + owned_blocks.assign(local_count, PartitionID{0}); + owned_assigned.assign(local_count, static_cast(0)); + auto const source_limit = std::min(dense_received.segment_count(), + static_cast(size)); + for (auto source = std::size_t{0}; source < source_limit; ++source) { + for (auto const& update : dense_received.segment(source)) { + auto const owner = ownership.owner(update.coarse_global_id); + auto const local_id = Q.find_local_id(update.coarse_global_id); + auto const local_id_is_representable = + local_id.has_value() && std::in_range(*local_id); + auto const index = local_id_is_representable + ? static_cast(*local_id) + : std::size_t{0}; + auto const record_is_valid = + owner.has_value() && *owner == rank_index && + local_id_is_representable && index < owned_blocks.size() && + update.coarse_global_id < number_of_coarse_nodes && + update.block < number_of_blocks; + dense_received_is_valid = dense_received_is_valid && record_is_valid; + if (!record_is_valid) { + continue; } - - if( m_messages[ rank ].size() != 0 ) { - for( ULONG i = 0; i < (ULONG)m_messages[rank].size()-1; i+=2) { - NodeID globalID = m_messages[rank][i]; - NodeID node = Q.getLocalID(globalID); - NodeWeight block = m_messages[rank][i+1]; - Q.setSecondPartitionIndex(node , block); - } + dense_received_is_valid = + dense_received_is_valid && + Q.getGlobalID(*local_id) == update.coarse_global_id && + (owned_assigned[index] == 0 || owned_blocks[index] == update.block); + if (owned_assigned[index] == 0) { + owned_blocks[index] = update.block; + owned_assigned[index] = 1; } + } + } + dense_received_is_valid = + dense_received_is_valid && + std::ranges::all_of(owned_assigned, + [](auto assigned) { return assigned != 0; }); + } catch (...) { + mpi::abort_on_exception(plan.topology().native_handle(), + "block-down dense receive staging"); + } + if (!mpi::detail::collective_predicate(dense_received_is_valid, + plan.topology().view())) { + mpi::throw_collectively_agreed_semantic_error( + plan.topology().native_handle(), + "block-down dense received validation failed"); + } - PEID counter = 0; - while( counter < size - 1) { - // wait for incomming message of an adjacent processor - MPI_Status st; - MPI_Probe(MPI_ANY_SOURCE, rank+10*size, communicator, &st); - - int message_length; - MPI_Get_count(&st, MPI_UNSIGNED_LONG_LONG, &message_length); - - std::vector incmessage; incmessage.resize(message_length); - - MPI_Status rst; - MPI_Recv( &incmessage[0], message_length, MPI_UNSIGNED_LONG_LONG, st.MPI_SOURCE, rank+10*size, communicator, &rst); - counter++; - - // now integrate the changes - if( incmessage[0] == std::numeric_limits< NodeID >::max()) continue; // nothing to do - - for( ULONG i = 0; i < incmessage.size()-1; i+=2) { - NodeID globalID = incmessage[i]; - NodeWeight block = incmessage[i+1]; - NodeID node = Q.getLocalID(globalID); - Q.setSecondPartitionIndex( node , block); - } + auto neighbor_sends = mpi::segmented_buffer{}; + auto neighbor_outgoing_is_valid = true; + try { + auto updates_by_destination = + std::vector>( + plan.topology().destinations().size()); + for (auto destination_index = std::size_t{0}; + destination_index < plan.topology().destinations().size(); + ++destination_index) { + auto const local_nodes = plan.outgoing_local_nodes(destination_index); + auto& updates = updates_by_destination[destination_index]; + updates.reserve(local_nodes.size()); + auto previous = std::optional{}; + for (auto const local : local_nodes) { + auto const local_is_representable = std::in_range(local); + auto const index = local_is_representable + ? static_cast(local) + : std::size_t{0}; + auto const local_is_valid = + local_is_representable && local < Q.number_of_local_nodes() && + index < owned_blocks.size() && owned_assigned[index] != 0 && + (!previous.has_value() || *previous < local); + neighbor_outgoing_is_valid = + neighbor_outgoing_is_valid && local_is_valid; + if (!local_is_valid) { + continue; } + auto const global_id = Q.getGlobalID(local); + neighbor_outgoing_is_valid = neighbor_outgoing_is_valid && + Q.is_interface_node(local) && + global_id < number_of_coarse_nodes && + Q.find_local_id(global_id) == local && + owned_blocks[index] < number_of_blocks; + updates.push_back({global_id, owned_blocks[index]}); + previous = local; + } + } + neighbor_sends = + mpi::segmented_buffer::from_segments( + updates_by_destination); + } catch (...) { + mpi::abort_on_exception(plan.topology().native_handle(), + "block-down neighbor send staging"); + } + if (!mpi::detail::collective_predicate(neighbor_outgoing_is_valid, + plan.topology().view())) { + mpi::throw_collectively_agreed_semantic_error( + plan.topology().native_handle(), + "block-down neighbor outgoing validation failed"); + } - update_ghost_nodes_blocks( communicator, Q ); -} - -void parallel_block_down_propagation::update_ghost_nodes_blocks( MPI_Comm communicator, parallel_graph_access & G ) { - PEID rank, size; - MPI_Comm_rank( communicator, &rank); - MPI_Comm_size( communicator, &size); - - m_send_buffers.resize(size); - std::vector< bool > PE_packed(size, false); - forall_local_nodes(G, node) { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if( !G.is_local_node(target) ) { - PEID peID = G.getTargetPE(target); - if( !PE_packed[peID] ) { // make sure a node is sent at most once - m_send_buffers[peID].push_back(G.getGlobalID(node)); - m_send_buffers[peID].push_back(G.getSecondPartitionIndex(node)); - PE_packed[peID] = true; - } - } - } endfor - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if( !G.is_local_node(target) ) { - PE_packed[G.getTargetPE(target)] = false; - } - } endfor - } endfor - - //send all neighbors their packages using Isends - //a neighbor that does not receive something gets a specific token - for( PEID peID = 0; peID < (PEID)m_send_buffers.size(); peID++) { - if( G.is_adjacent_PE(peID) ) { - //now we have to send a message - if( m_send_buffers[peID].size() == 0 ){ - // length 1 encode no message - m_send_buffers[peID].push_back(0); - } - - MPI_Request rq; - MPI_Isend( &m_send_buffers[peID][0], - m_send_buffers[peID].size(), MPI_UNSIGNED_LONG_LONG, peID, peID+11*size, communicator, &rq); - } + auto neighbor_received = + mpi::neighbor_all_to_all_v(std::move(neighbor_sends), plan.topology()); + using pending_ghost_update = std::tuple; + auto pending_ghost_updates = std::vector{}; + auto ghost_blocks = std::vector{}; + auto ghost_assigned = std::vector{}; + auto neighbor_received_is_valid = + neighbor_received.segment_count() == plan.topology().sources().size(); + try { + auto const ghost_count = + static_cast(Q.number_of_ghost_nodes()); + auto const ghost_begin = Q.number_of_local_nodes() + NodeID{1}; + ghost_blocks.assign(ghost_count, PartitionID{0}); + ghost_assigned.assign(ghost_count, static_cast(0)); + pending_ghost_updates.reserve(ghost_count); + auto const source_limit = std::min(neighbor_received.segment_count(), + plan.topology().sources().size()); + for (auto source_index = std::size_t{0}; source_index < source_limit; + ++source_index) { + auto const source = plan.topology().sources()[source_index]; + auto const updates = neighbor_received.segment(source_index); + auto const expected = plan.expected_ghost_nodes(source_index); + auto received_ids = std::vector{}; + received_ids.reserve(updates.size()); + neighbor_received_is_valid = + neighbor_received_is_valid && updates.size() == expected.size(); + for (auto update_index = std::size_t{0}; update_index < updates.size(); + ++update_index) { + auto const& update = updates[update_index]; + received_ids.push_back(update.coarse_global_id); + auto const owner = ownership.owner(update.coarse_global_id); + auto const source_is_representable = std::in_range(source); + auto const local_id = + Q.find_ghost_local_id(update.coarse_global_id, source); + auto const local_id_is_representable = + local_id.has_value() && std::in_range(*local_id); + auto const local_id_is_ghost = + local_id_is_representable && *local_id >= ghost_begin; + auto const ghost_index_node = + local_id_is_ghost ? *local_id - ghost_begin : NodeID{0}; + auto const ghost_index_is_representable = + local_id_is_ghost && std::in_range(ghost_index_node); + auto const ghost_index = + ghost_index_is_representable + ? static_cast(ghost_index_node) + : std::size_t{0}; + auto const record_is_valid = + owner.has_value() && source_is_representable && + *owner == static_cast(source) && + update.coarse_global_id < number_of_coarse_nodes && + update.block < number_of_blocks && ghost_index_is_representable && + ghost_index < ghost_blocks.size() && + ghost_assigned[ghost_index] == 0; + neighbor_received_is_valid = + neighbor_received_is_valid && record_is_valid; + if (!record_is_valid) { + continue; } + neighbor_received_is_valid = + neighbor_received_is_valid && + Q.getGlobalID(*local_id) == update.coarse_global_id; + ghost_blocks[ghost_index] = update.block; + ghost_assigned[ghost_index] = 1; + pending_ghost_updates.emplace_back(update.coarse_global_id, source, + *local_id, update.block); + } + std::ranges::sort(received_ids); + neighbor_received_is_valid = + neighbor_received_is_valid && + std::ranges::adjacent_find(received_ids) == received_ids.end() && + std::ranges::equal(received_ids, expected); + } + std::ranges::sort(pending_ghost_updates, {}, [](auto const& update) { + return std::tie(std::get<0>(update), std::get<1>(update)); + }); + neighbor_received_is_valid = + neighbor_received_is_valid && + std::ranges::all_of(ghost_assigned, + [](auto assigned) { return assigned != 0; }) && + std::ranges::adjacent_find( + pending_ghost_updates, [](auto const& lhs, auto const& rhs) { + return std::get<0>(lhs) == std::get<0>(rhs); + }) == pending_ghost_updates.end(); + } catch (...) { + mpi::abort_on_exception(plan.topology().native_handle(), + "block-down neighbor receive staging"); + } + if (!mpi::detail::collective_predicate(neighbor_received_is_valid, + plan.topology().view())) { + mpi::throw_collectively_agreed_semantic_error( + plan.topology().native_handle(), + "block-down neighbor received validation failed"); + } - //receive incomming - PEID counter = 0; - while( counter < G.getNumberOfAdjacentPEs()) { - // wait for incomming message of an adjacent processor - MPI_Status st; - MPI_Probe(MPI_ANY_SOURCE, rank+11*size, communicator, &st); - - int message_length; - MPI_Get_count(&st, MPI_UNSIGNED_LONG_LONG, &message_length); - std::vector message; message.resize(message_length); - - MPI_Status rst; - MPI_Recv( &message[0], message_length, MPI_UNSIGNED_LONG_LONG, st.MPI_SOURCE, rank+11*size, communicator, &rst); - counter++; - - // now integrate the changes - if(message_length == 1) continue; // nothing to do - - for( int i = 0; i < message_length-1; i+=2) { - NodeID global_id = message[i]; - NodeWeight block = message[i+1]; - - G.setSecondPartitionIndex( G.getLocalID(global_id), block ); - } - } + try { + for (auto local = std::size_t{0}; local < owned_blocks.size(); ++local) { + Q.setSecondPartitionIndex(static_cast(local), + owned_blocks[local]); + } + for (auto const& [global_id, source, local_id, block] : + pending_ghost_updates) { + static_cast(global_id); + static_cast(source); + Q.setSecondPartitionIndex(local_id, block); + } + } catch (...) { + mpi::abort_on_exception(plan.topology().native_handle(), + "block-down state commit"); + } + try { + for (auto local = std::size_t{0}; local < owned_blocks.size(); ++local) { + auto const node = static_cast(local); + KAHIP_MPI_TRACE(mpi::trace::block_propagation( + mpi::trace::current_hierarchy(), Q.getGlobalID(node), rank, rank, + owned_blocks[local])); + } + for (auto const& [global_id, source, local_id, block] : + pending_ghost_updates) { + static_cast(local_id); + KAHIP_MPI_TRACE(mpi::trace::block_propagation( + mpi::trace::current_hierarchy(), global_id, source, rank, block)); + } + } catch (...) { + mpi::abort_on_exception(plan.topology().native_handle(), + "block-down trace commit"); + } } +} // namespace parhip diff --git a/parallel/parallel_src/lib/parallel_contraction_projection/parallel_block_down_propagation.h b/parallel/parallel_src/lib/parallel_contraction_projection/parallel_block_down_propagation.h index d0b36ab2..81659299 100644 --- a/parallel/parallel_src/lib/parallel_contraction_projection/parallel_block_down_propagation.h +++ b/parallel/parallel_src/lib/parallel_contraction_projection/parallel_block_down_propagation.h @@ -8,25 +8,37 @@ #ifndef PARALLEL_BLOCK_DOWN_PROPAGATION_SRTCMH8F #define PARALLEL_BLOCK_DOWN_PROPAGATION_SRTCMH8F +#include +#include + +#include "communication/mpi_types.h" #include "data_structure/parallel_graph_access.h" #include "partition_config.h" +namespace parhip { +namespace block_down { +struct block_update { + NodeID coarse_global_id; + PartitionID block; +}; +} // namespace block_down -class parallel_block_down_propagation { -public: - parallel_block_down_propagation(); - virtual ~parallel_block_down_propagation(); - - void propagate_block_down( MPI_Comm communicator, PPartitionConfig & config, - parallel_graph_access & G, - parallel_graph_access & Q); - -private: - - void update_ghost_nodes_blocks( MPI_Comm communicator, parallel_graph_access & G ); +static_assert(std::is_standard_layout_v); +static_assert(std::is_trivially_copyable_v); - std::vector< std::vector< NodeID > > m_messages; - std::vector< std::vector< NodeID > > m_send_buffers; // buffers to send messages +class parallel_block_down_propagation { + public: + void propagate_block_down(MPI_Comm communicator, + PPartitionConfig& config, + parallel_graph_access& G, + parallel_graph_access& Q); }; +} // namespace parhip +template <> +struct parhip::mpi::wire_members { + inline static constexpr auto value = std::tuple{ + &parhip::block_down::block_update::coarse_global_id, + &parhip::block_down::block_update::block}; +}; #endif /* end of include guard: PARALLEL_BLOCK_DOWN_PROPAGATION_SRTCMH8F */ diff --git a/parallel/parallel_src/lib/parallel_contraction_projection/parallel_contraction.cpp b/parallel/parallel_src/lib/parallel_contraction_projection/parallel_contraction.cpp index 83cf67b9..ec0a3d04 100644 --- a/parallel/parallel_src/lib/parallel_contraction_projection/parallel_contraction.cpp +++ b/parallel/parallel_src/lib/parallel_contraction_projection/parallel_contraction.cpp @@ -6,631 +6,970 @@ *****************************************************************************/ #include "parallel_contraction.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "communication/contiguous_owner_layout.h" +#include "communication/ghost_exchange_plan.h" +#include "communication/mpi_adapter.h" +#include "communication/mpi_trace.h" #include "data_structure/hashed_graph.h" #include "tools/helpers.h" - -parallel_contraction::parallel_contraction() { - -} - -parallel_contraction::~parallel_contraction() { - -} - -void parallel_contraction::contract_to_distributed_quotient( MPI_Comm communicator, PPartitionConfig & config, +namespace parhip { +void parallel_contraction::contract_to_distributed_quotient( MPI_Comm communicator, PPartitionConfig & config, parallel_graph_access & G, parallel_graph_access & Q) { - - NodeID number_of_distinct_labels; // equals global number of coarse nodes - - // maps old ids to new ids in interval [0, ...., num_of_distinct_labels - // and stores this information only for the local nodes - std::unordered_map< NodeID, NodeID > label_mapping; - - compute_label_mapping( communicator, G, number_of_distinct_labels, label_mapping); - - // compute the projection table - G.allocate_node_to_cnode(); - forall_local_nodes(G, node) { - G.setCNode( node, label_mapping[ G.getNodeLabel( node )]); - } endfor - - get_nodes_to_cnodes_ghost_nodes( communicator, G ); - - //now we can really build the edges of the quotient graph - hashed_graph hG; - std::unordered_map< NodeID, NodeWeight > node_weights; - - build_quotient_graph_locally( G, number_of_distinct_labels, hG, node_weights); - - MPI_Barrier(communicator); - - m_messages.resize(0); - std::vector< std::vector< NodeID > >(m_messages).swap(m_messages); - m_out_messages.resize(0); - std::vector< std::vector< NodeID > >(m_out_messages).swap(m_out_messages); - m_send_buffers.resize(0); - std::vector< std::vector< NodeID > >(m_send_buffers).swap(m_send_buffers); - - redistribute_hased_graph_and_build_graph_locally( communicator, hG, node_weights, number_of_distinct_labels, Q ); - update_ghost_nodes_weights( communicator, Q ); +#if KAHIP_ENABLE_MPI_TRACE + auto const trace_rank = mpi::communicator_view{communicator}.rank(); +#endif + + NodeID number_of_distinct_labels; // equals global number of coarse nodes + + // maps old ids to new ids in interval [0, ...., num_of_distinct_labels + // and stores this information only for the local nodes + std::unordered_map< NodeID, NodeID > label_mapping; + + compute_label_mapping( communicator, G, number_of_distinct_labels, label_mapping); + + // Compute and commit the complete local/ghost projection table as one + // transaction. Trace records are emitted only after the commit succeeds. + get_nodes_to_cnodes_ghost_nodes(communicator, G, number_of_distinct_labels, + label_mapping); + + //now we can really build the edges of the quotient graph + hashed_graph hG; + std::unordered_map< NodeID, NodeWeight > node_weights; + + build_quotient_graph_locally(communicator, G, number_of_distinct_labels, hG, + node_weights); + + mpi::check_or_abort( + MPI_Barrier(communicator), communicator, "MPI_Barrier(contraction)"); + + redistribute_hased_graph_and_build_graph_locally( communicator, hG, node_weights, number_of_distinct_labels, Q ); + update_ghost_nodes_weights( communicator, Q ); + forall_local_nodes(Q, node) { + KAHIP_MPI_TRACE(mpi::trace::quotient_node_weight( + mpi::trace::current_hierarchy(), Q.getGlobalID(node), trace_rank, + Q.getNodeWeight(node))); + forall_out_edges(Q, edge, node) { + auto const target = Q.getEdgeTarget(edge); + KAHIP_MPI_TRACE(mpi::trace::quotient_edge( + mpi::trace::current_hierarchy(), Q.getGlobalID(node), trace_rank, + Q.getGlobalID(target), Q.getEdgeWeight(edge))); + } endfor + } endfor } -void parallel_contraction::compute_label_mapping( MPI_Comm communicator, parallel_graph_access & G, - NodeID & global_num_distinct_ids, - std::unordered_map< NodeID, NodeID > & label_mapping ) { - PEID rank, size; - MPI_Comm_rank( communicator, &rank); - MPI_Comm_size( communicator, &size); - - NodeID divisor = ceil( G.number_of_global_nodes()/ (double)size); - - helpers helper; - m_messages.resize(size); - - std::vector< std::unordered_map< NodeID, bool > > filter; - filter.resize(size); - forall_local_nodes(G, node) { - PEID peID = G.getNodeLabel(node) / divisor; - filter[ peID ][G.getNodeLabel(node)] = true; - } endfor - - for( PEID peID = 0; peID < (PEID) size; peID++) { - std::unordered_map< NodeID, bool >::iterator it; - for( it = filter[peID].begin(); it != filter[peID].end(); it++) { - m_messages[peID].push_back(it->first); - } - } - - // now flood the network - for( PEID peID = 0; peID < size; peID++) { - if( peID != rank ) { - if( m_messages[peID].size() == 0 ){ - m_messages[peID].push_back(std::numeric_limits::max()); - } - - MPI_Request rq; - MPI_Isend( &m_messages[peID][0], - m_messages[peID].size(), - MPI_UNSIGNED_LONG_LONG, - peID, peID+4*size, communicator, &rq); - } - } - std::vector< std::vector< NodeID > > local_labels_byPE; - local_labels_byPE.resize(size); - - for( ULONG i = 0; i < m_messages[rank].size(); i++) { - local_labels_byPE[rank].push_back(m_messages[rank][i]); - } - - - std::vector< std::vector< NodeID > > inc_messages; - inc_messages.resize(size); - - PEID counter = 0; - while( counter < size - 1) { - // wait for incomming message of an adjacent processor - MPI_Status st; - MPI_Probe(MPI_ANY_SOURCE, rank+4*size, communicator, &st); - - int message_length; - MPI_Get_count(&st, MPI_UNSIGNED_LONG_LONG, &message_length); - std::vector incmessage; incmessage.resize(message_length); - - MPI_Status rst; - MPI_Recv( &incmessage[0], message_length, MPI_UNSIGNED_LONG_LONG, st.MPI_SOURCE, rank+4*size, communicator, &rst); - counter++; - - PEID peID = st.MPI_SOURCE; - for( int i = 0; i < message_length; i++) { - inc_messages[peID].push_back(incmessage[i]); - } // store those because we need to send them their mapping back - - // now integrate the changes - if( incmessage[0] == std::numeric_limits< NodeID >::max()) continue; // nothing to do +// MPI AlltoAll based implementation +void parallel_contraction::compute_label_mapping( + MPI_Comm communicator, + parallel_graph_access& G, + NodeID& global_num_distinct_ids, + std::unordered_map& label_mapping) { + auto const communicator_view = mpi::communicator_view{communicator}; + auto const rank = communicator_view.rank(); + auto const size = communicator_view.size(); + auto const rank_index = static_cast(rank); + + auto const number_of_global_nodes = mpi::agree_collectively( + G.number_of_global_nodes(), + communicator_view, + "label global node count agreement failed"); + auto const ownership = mpi::contiguous_owner_layout{ + number_of_global_nodes, static_cast(size)}; + + auto requests_by_destination = + std::vector>( + static_cast(size)); + std::unordered_set requested_labels; + + auto local_requests_are_valid = true; + forall_local_nodes(G, node) { + local_requests_are_valid = + local_requests_are_valid && + ownership.owner(G.getNodeLabel(node)).has_value(); + } endfor + mpi::validate_collectively( + local_requests_are_valid, + mpi::communicator_view{communicator}, + "label request local validation failed"); + + forall_local_nodes(G, node) { + auto const old_label = G.getNodeLabel(node); + auto const destination = ownership.owner(old_label).value(); + requests_by_destination.at(destination).push_back({old_label}); + requested_labels.insert(old_label); + } endfor + + for (auto& requests : requests_by_destination) { + std::ranges::stable_sort(requests, {}, [](auto const& request) { + return request.old_label; + }); + auto const unique_end = std::ranges::unique( + requests, {}, [](auto const& request) { return request.old_label; }); + requests.erase(unique_end.begin(), unique_end.end()); + } + + auto incoming_requests = mpi::all_to_all_v( + mpi::segmented_buffer::from_segments( + requests_by_destination), + mpi::communicator_view{communicator}); + + auto incoming_requests_are_valid = true; + for (std::size_t source = 0; source < incoming_requests.segment_count(); + ++source) { + for (auto const& request : incoming_requests.segment(source)) { + incoming_requests_are_valid = + incoming_requests_are_valid && + ownership.owner(request.old_label) == rank_index; + } + } + mpi::validate_collectively( + incoming_requests_are_valid, + mpi::communicator_view{communicator}, + "label request owner validation failed"); + + std::vector local_labels; + local_labels.reserve(incoming_requests.storage().size()); + for (std::size_t source = 0; source < incoming_requests.segment_count(); + ++source) { + for (auto const& request : incoming_requests.segment(source)) { + local_labels.push_back(request.old_label); + } + } + + helpers helper; + helper.filter_duplicates( + local_labels, + [](NodeID const& lhs, NodeID const& rhs) -> bool { return (lhs < rhs); }, + [](NodeID const& lhs, NodeID const& rhs) -> bool { + return (lhs == rhs); + }); + // afterward they are sorted! + + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + // %%%%%%%%%%%%%%%%%%%%%%%Labels are unique on all PEs%%%%%%%%%%%%%%%%%%%% + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + // now counting + + NodeID local_num_labels = local_labels.size(); + NodeID prefix_sum = 0; + + mpi::check_or_abort(MPI_Scan(&local_num_labels, + &prefix_sum, + 1, + MPI_UNSIGNED_LONG_LONG, + MPI_SUM, + communicator), + communicator, + "MPI_Scan(label prefix)"); + + global_num_distinct_ids = prefix_sum; + // Broadcast global number of ids + mpi::check_or_abort(MPI_Bcast(&global_num_distinct_ids, + 1, + MPI_UNSIGNED_LONG_LONG, + size - 1, + communicator), + communicator, + "MPI_Bcast(global label count)"); + + NodeID num_smaller_ids = prefix_sum - local_num_labels; + + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + // %%%%%Now Build the mapping and send information back to PEs%%%%%%%%%%%% + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + // build the mapping locally + std::unordered_map label_mapping_to_cnode; + NodeID cur_id = num_smaller_ids; + for (ULONG i = 0; i < local_labels.size(); i++) { + label_mapping_to_cnode[local_labels[i]] = cur_id++; + } + + auto replies_by_destination = + std::vector>( + static_cast(size)); + for (std::size_t source = 0; source < incoming_requests.segment_count(); + ++source) { + auto& replies = replies_by_destination[source]; + for (auto const& request : incoming_requests.segment(source)) { + replies.push_back({request.old_label, + label_mapping_to_cnode.at(request.old_label)}); + } + std::ranges::stable_sort(replies, {}, [](auto const& reply) { + return std::tie(reply.old_label, reply.coarse_global_id); + }); + } + + auto incoming_replies = mpi::all_to_all_v( + mpi::segmented_buffer::from_segments( + replies_by_destination), + mpi::communicator_view{communicator}); + auto incoming_replies_are_valid = true; + for (std::size_t source = 0; source < incoming_replies.segment_count(); + ++source) { + for (auto const& reply : incoming_replies.segment(source)) { + auto const owner = ownership.owner(reply.old_label); + if (!owner.has_value()) { + incoming_replies_are_valid = false; + continue; + } + incoming_replies_are_valid = + incoming_replies_are_valid && + *owner == source && + reply.coarse_global_id < global_num_distinct_ids && + requested_labels.contains(reply.old_label); + if (requested_labels.erase(reply.old_label) == 0) { + incoming_replies_are_valid = false; + } + } + } + incoming_replies_are_valid = + incoming_replies_are_valid && requested_labels.empty(); + mpi::validate_collectively( + incoming_replies_are_valid, + mpi::communicator_view{communicator}, + "label reply validation failed"); + for (auto const& reply : incoming_replies.storage()) { + label_mapping[reply.old_label] = reply.coarse_global_id; + } +} - for( int i = 0; i < message_length; i++) { - local_labels_byPE[peID].push_back(incmessage[i]); - } +void parallel_contraction::get_nodes_to_cnodes_ghost_nodes( + MPI_Comm communicator, + parallel_graph_access& G, + NodeID number_of_distinct_labels, + std::unordered_map const& label_mapping) { + auto const graph_communicator = mpi::communicator_view{G.getCommunicator()}; + + auto communicator_is_compatible = communicator != MPI_COMM_NULL; + if (communicator_is_compatible) { + auto comparison = int{MPI_UNEQUAL}; + mpi::check_or_abort( + MPI_Comm_compare(communicator, G.getCommunicator(), &comparison), + G.getCommunicator(), "MPI_Comm_compare(contraction ghost CNodes)"); + communicator_is_compatible = + comparison == MPI_IDENT || comparison == MPI_CONGRUENT; + } + mpi::validate_collectively( + communicator_is_compatible, graph_communicator, + "contraction ghost CNode communicator validation failed"); + + auto const agreed_coarse_count = mpi::agree_collectively( + number_of_distinct_labels, graph_communicator, + "contraction ghost CNode coarse count agreement failed"); + auto const agreed_global_count = mpi::agree_collectively( + G.number_of_global_nodes(), graph_communicator, + "contraction ghost CNode global count agreement failed"); + + auto staged = std::vector{}; + auto assigned = std::vector{}; + auto local_is_valid = true; + try { + auto const storage_size = G.node_to_cnode_storage_size(); + staged.resize(storage_size); + assigned.assign(storage_size, static_cast(0)); + + auto const local_count = G.number_of_local_nodes(); + auto const ghost_count = G.number_of_ghost_nodes(); + local_is_valid = std::in_range(local_count) && + std::in_range(ghost_count); + if (local_is_valid) { + auto const local_size = static_cast(local_count); + auto const ghost_size = static_cast(ghost_count); + local_is_valid = storage_size > local_size && + storage_size - local_size - std::size_t{1} == ghost_size; + } + + for (NodeID local = 0; local < local_count; ++local) { + auto const label = G.getNodeLabel(local); + auto const mapping = label_mapping.find(label); + auto const global_id = G.getGlobalID(local); + auto const roundtrip = G.find_local_id(global_id); + local_is_valid = local_is_valid && mapping != label_mapping.end() && + global_id < agreed_global_count && roundtrip == local; + if (mapping == label_mapping.end() || + mapping->second >= agreed_coarse_count || + !std::in_range(local)) { + local_is_valid = false; + continue; + } + auto const index = static_cast(local); + if (index >= staged.size() || assigned[index] != 0) { + local_is_valid = false; + continue; + } + staged[index] = mapping->second; + assigned[index] = 1; + } + if (agreed_coarse_count == 0 && G.number_of_local_nodes() != 0) { + local_is_valid = false; + } + } catch (...) { + mpi::abort_on_exception(G.getCommunicator(), + "contraction ghost CNode local staging"); + } + + mpi::validate_collectively(local_is_valid, graph_communicator, + "contraction ghost CNode local validation failed"); + + auto const& plan = G.ghost_plan(); + auto semantic_failure = std::string_view{}; + try { + auto outgoing = + std::vector>( + plan.topology().destinations().size()); + auto outgoing_is_valid = true; + for (std::size_t destination_index = 0; + destination_index < plan.topology().destinations().size(); + ++destination_index) { + auto const local_nodes = plan.outgoing_local_nodes(destination_index); + auto& records = outgoing[destination_index]; + records.reserve(local_nodes.size()); + auto previous = std::optional{}; + for (auto const local : local_nodes) { + auto const local_is_representable = std::in_range(local); + auto const index = local_is_representable + ? static_cast(local) + : std::size_t{0}; + outgoing_is_valid = outgoing_is_valid && local_is_representable && + local < G.number_of_local_nodes() && + index < staged.size() && assigned[index] != 0 && + (!previous.has_value() || *previous < local); + if (!local_is_representable || local >= G.number_of_local_nodes() || + index >= staged.size() || assigned[index] == 0) { + continue; } - - std::vector< NodeID > local_labels; - for( PEID peID = 0; peID < size; peID++) { - for( ULONG i = 0; i < local_labels_byPE[peID].size(); i++) { - local_labels.push_back(local_labels_byPE[peID][i]); - } + auto const global_id = G.getGlobalID(local); + outgoing_is_valid = outgoing_is_valid && + global_id < agreed_global_count && + G.find_local_id(global_id) == local && + staged[index] < agreed_coarse_count; + records.push_back({global_id, staged[index]}); + previous = local; + } + } + outgoing_is_valid = + outgoing_is_valid && + (agreed_coarse_count != 0 || + std::ranges::all_of( + outgoing, + &std::vector::empty)); + if (!mpi::detail::collective_predicate(outgoing_is_valid, + plan.topology().view())) { + semantic_failure = "contraction ghost CNode outgoing validation failed"; + } else { + auto received = mpi::neighbor_all_to_all_v( + mpi::segmented_buffer< + contraction::ghost_cnode_assignment>::from_segments(outgoing), + plan.topology()); + + auto resolved = + std::vector>(plan.topology().sources().size()); + auto structure_is_valid = + received.segment_count() == plan.topology().sources().size(); + auto const source_limit = + std::min(received.segment_count(), plan.topology().sources().size()); + for (std::size_t source_index = 0; source_index < source_limit; + ++source_index) { + auto const source = plan.topology().sources()[source_index]; + auto const records = received.segment(source_index); + auto const expected = plan.expected_ghost_nodes(source_index); + auto& local_ids = resolved[source_index]; + local_ids.reserve(records.size()); + auto received_ids = std::vector{}; + received_ids.reserve(records.size()); + for (auto const& record : records) { + received_ids.push_back(record.global_id); + auto const local_id = G.find_ghost_local_id(record.global_id, source); + structure_is_valid = structure_is_valid && + record.global_id < agreed_global_count && + record.coarse_global_id < agreed_coarse_count && + local_id.has_value(); + local_ids.push_back(local_id.value_or(NodeID{0})); } - - - // filter duplicates locally - helper.filter_duplicates( local_labels, - [](const NodeID & lhs, const NodeID & rhs) -> bool { - return (lhs < rhs); - }, - [](const NodeID & lhs, const NodeID & rhs) -> bool { - return (lhs == rhs); - }); - //afterwards they are sorted! - - // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - // %%%%%%%%%%%%%%%%%%%%%%%Labels are unique on all PEs%%%%%%%%%%%%%%%%%%%% - // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - // now counting - - NodeID local_num_labels = local_labels.size(); - NodeID prefix_sum = 0; - - MPI_Scan(&local_num_labels, &prefix_sum, 1, MPI_UNSIGNED_LONG_LONG, MPI_SUM, communicator); - - global_num_distinct_ids = prefix_sum; - // Broadcast global number of ids - MPI_Bcast(&global_num_distinct_ids, 1, MPI_UNSIGNED_LONG_LONG, size-1, communicator); - - NodeID num_smaller_ids = prefix_sum - local_num_labels; - - // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - // %%%%%Now Build the mapping and send information back to PEs%%%%%%%%%%%% - // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - // build the mapping locally - std::unordered_map< NodeID, NodeID > label_mapping_to_cnode; - NodeID cur_id = num_smaller_ids; - for( ULONG i = 0; i < local_labels.size(); i++) { - label_mapping_to_cnode[local_labels[i]] = cur_id++; + std::ranges::sort(received_ids); + structure_is_valid = + structure_is_valid && records.size() == expected.size() && + std::ranges::adjacent_find(received_ids) == received_ids.end() && + std::ranges::equal(received_ids, expected); + } + + if (!mpi::detail::collective_predicate(structure_is_valid, + plan.topology().view())) { + semantic_failure = "contraction ghost CNode received validation failed"; + } else { + auto staging_is_complete = true; + for (std::size_t source_index = 0; + source_index < plan.topology().sources().size(); ++source_index) { + auto const records = received.segment(source_index); + auto const& local_ids = resolved[source_index]; + staging_is_complete = + staging_is_complete && records.size() == local_ids.size(); + for (std::size_t record_index = 0; record_index < records.size(); + ++record_index) { + auto const local_id = local_ids[record_index]; + auto const representable = std::in_range(local_id); + auto const index = representable + ? static_cast(local_id) + : std::size_t{0}; + staging_is_complete = staging_is_complete && representable && + local_id > G.number_of_local_nodes() && + index < staged.size() && assigned[index] == 0; + if (!representable || local_id <= G.number_of_local_nodes() || + index >= staged.size() || assigned[index] != 0) { + continue; + } + staged[index] = records[record_index].coarse_global_id; + assigned[index] = 1; + } } - // now send the processes the mapping back - //std::vector< std::vector< NodeID > > m_out_messages; - m_out_messages.resize(size); - - for( PEID peID = 0; peID < (PEID)size; peID++) { - if( peID == rank ) continue; - - if( inc_messages[peID][0] == std::numeric_limits::max()) { - m_out_messages[peID].push_back(std::numeric_limits::max()); - continue; - } - - for( ULONG i = 0; i < inc_messages[peID].size(); i++) { - m_out_messages[peID].push_back( label_mapping_to_cnode[ inc_messages[peID][i] ] ); - } + auto const local_count = + static_cast(G.number_of_local_nodes()); + staging_is_complete = staging_is_complete && + staged.size() == G.node_to_cnode_storage_size() && + local_count < assigned.size() && + assigned[local_count] == 0; + for (std::size_t index = 0; index < assigned.size(); ++index) { + if (index != local_count) { + staging_is_complete = staging_is_complete && assigned[index] != 0; + } } - - for( PEID peID = 0; peID < size; peID++) { - if( peID != rank ) { - MPI_Request rq; - MPI_Isend( &m_out_messages[peID][0], - m_out_messages[peID].size(), - MPI_UNSIGNED_LONG_LONG, - peID, peID+5*size, communicator, &rq); - } - } - - // first the local labels - for( ULONG i = 0; i < m_messages[rank].size(); i++) { - label_mapping[ m_messages[rank][i] ] = label_mapping_to_cnode[m_messages[rank][i]]; - } - - counter = 0; - while( counter < size - 1) { - // wait for incomming message of an adjacent processor - MPI_Status st; - MPI_Probe(MPI_ANY_SOURCE, rank+5*size, communicator, &st); - - int message_length; - MPI_Get_count(&st, MPI_UNSIGNED_LONG_LONG, &message_length); - std::vector incmessage; incmessage.resize(message_length); - - MPI_Status rst; - MPI_Recv( &incmessage[0], message_length, MPI_UNSIGNED_LONG_LONG, st.MPI_SOURCE, rank+5*size, communicator, &rst); - counter++; - - // now integrate the changes - if( incmessage[0] == std::numeric_limits< NodeID >::max()) continue; // nothing to do - - PEID peID = st.MPI_SOURCE; - for( int i = 0; i < message_length; i++) { - label_mapping[ m_messages[peID][i] ] = incmessage[i]; - } + if (!mpi::detail::collective_predicate(staging_is_complete, + plan.topology().view())) { + semantic_failure = + "contraction ghost CNode staging validation failed"; + } else { + G.replace_node_to_cnode(std::move(staged)); +#if KAHIP_ENABLE_MPI_TRACE + auto const trace_rank = mpi::communicator_view{communicator}.rank(); +#endif + forall_local_nodes(G, node) { + KAHIP_MPI_TRACE(mpi::trace::contraction_label( + mpi::trace::current_hierarchy(), G.getGlobalID(node), + trace_rank, G.getNodeLabel(node), G.getCNode(node))); + } + endfor } + } + } + } catch (...) { + mpi::abort_on_exception(plan.topology().native_handle(), + "contraction ghost CNode exchange"); + } + + if (!semantic_failure.empty()) { + mpi::throw_collectively_agreed_semantic_error( + plan.topology().native_handle(), semantic_failure); + } } - -void parallel_contraction::get_nodes_to_cnodes_ghost_nodes( MPI_Comm communicator, parallel_graph_access & G ) { - PEID rank, size; - MPI_Comm_rank( communicator, &rank); - MPI_Comm_size( communicator, &size); - - std::vector< bool > PE_packed( size, false ); - m_send_buffers.resize( size ); - - forall_local_nodes(G, node) { - if(G.is_interface_node(node)) { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if( !G.is_local_node(target) ) { - PEID peID = G.getTargetPE(target); - if( !PE_packed[peID] ) { // make sure a node is sent at most once - m_send_buffers[peID].push_back(G.getGlobalID(node)); - m_send_buffers[peID].push_back(G.getCNode(node)); - PE_packed[peID] = true; - } - } - } endfor - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if( !G.is_local_node(target) ) { - PE_packed[G.getTargetPE(target)] = false; - } - } endfor - } - } endfor - - //send all neighbors their packages using Isends - //a neighbor that does not receive something gets a specific token - for( PEID peID = 0; peID < (PEID)m_send_buffers.size(); peID++) { - if( G.is_adjacent_PE(peID) ) { - //now we have to send a message - if( m_send_buffers[peID].size() == 0 ){ - // length 1 encode no message - m_send_buffers[peID].push_back(0); - } - - MPI_Request rq; - MPI_Isend( &m_send_buffers[peID][0], - m_send_buffers[peID].size(), MPI_UNSIGNED_LONG_LONG, peID, peID+6*size, communicator, &rq); - } - } - - ////receive incomming - PEID num_adjacent = G.getNumberOfAdjacentPEs(); - PEID counter = 0; - while( counter < num_adjacent ) { - // wait for incomming message of an adjacent processor - MPI_Status st; - MPI_Probe(MPI_ANY_SOURCE, rank+6*size, communicator, &st); - - int message_length; - MPI_Get_count(&st, MPI_UNSIGNED_LONG_LONG, &message_length); - std::vector message; message.resize(message_length); - - MPI_Status rst; - MPI_Recv( &message[0], message_length, MPI_UNSIGNED_LONG_LONG, st.MPI_SOURCE, rank+6*size, communicator, &rst); - counter++; - - // now integrate the changes - if(message_length == 1) continue; // nothing to do - - for( int i = 0; i < message_length-1; i+=2) { - NodeID global_id = message[i]; - NodeID cnode = message[i+1]; - - G.setCNode( G.getLocalID(global_id), cnode); - } +void parallel_contraction::build_quotient_graph_locally( + MPI_Comm communicator, + parallel_graph_access& G, + NodeID number_of_distinct_labels, + hashed_graph& hG, + std::unordered_map& node_weights) { + auto staged_graph = hashed_graph{}; + auto staged_node_weights = std::unordered_map{}; + auto node_weights_are_representable = true; + auto edge_weights_are_representable = true; + try { + forall_local_nodes(G, node) { + auto const coarse_node = G.getCNode(node); + auto [weight, inserted] = + staged_node_weights.try_emplace(coarse_node, NodeWeight{0}); + static_cast(inserted); + auto const sum = + contraction::checked_add(weight->second, G.getNodeWeight(node)); + node_weights_are_representable = + node_weights_are_representable && sum.has_value(); + if (sum.has_value()) { + weight->second = *sum; + } + + forall_out_edges(G, edge, node) { + auto const target = G.getEdgeTarget(edge); + auto const target_coarse_node = G.getCNode(target); + if (coarse_node != target_coarse_node) { + auto const key = hashed_edge{number_of_distinct_labels, coarse_node, + target_coarse_node}; + auto& aggregate = staged_graph[key].weight; + auto const edge_sum = + contraction::checked_add(aggregate, G.getEdgeWeight(edge)); + edge_weights_are_representable = + edge_weights_are_representable && edge_sum.has_value(); + if (edge_sum.has_value()) { + aggregate = *edge_sum; + } } + } + endfor + } + endfor + } catch (...) { + mpi::abort_on_exception(communicator, "local quotient aggregation staging"); + } + + mpi::validate_collectively(node_weights_are_representable, + mpi::communicator_view{communicator}, + "local quotient node-weight aggregation overflow"); + mpi::validate_collectively(edge_weights_are_representable, + mpi::communicator_view{communicator}, + "local quotient edge-weight aggregation overflow"); + hG.swap(staged_graph); + node_weights.swap(staged_node_weights); } - -void parallel_contraction::build_quotient_graph_locally( parallel_graph_access & G, - NodeID number_of_distinct_labels, - hashed_graph & hG, - std::unordered_map< NodeID, NodeWeight > & node_weights) { - forall_local_nodes(G, node) { - NodeID cur_cnode = G.getCNode( node ); - if( node_weights.find(cur_cnode) == node_weights.end()) { - node_weights[cur_cnode] = 0; - } - - node_weights[cur_cnode] += G.getNodeWeight( node ); - - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - NodeID target_cnode = G.getCNode(target); - if( cur_cnode != target_cnode ) { - // update the edge - hashed_edge he; - he.k = number_of_distinct_labels; - he.source = cur_cnode; - he.target = target_cnode; - - hG[he].weight += G.getEdgeWeight(e); - } - } endfor - } endfor -} - - - -void parallel_contraction::redistribute_hased_graph_and_build_graph_locally( MPI_Comm communicator, hashed_graph & hG, - std::unordered_map< NodeID, NodeWeight > & node_weights, - NodeID number_of_cnodes, - parallel_graph_access & Q ) { - PEID rank, size; - MPI_Comm_rank( communicator, &rank); - MPI_Comm_size( communicator, &size); - - NodeID divisor = ceil( number_of_cnodes/(double)size); - - //std::vector< std::vector< NodeID > > messages; - m_messages.resize(size); - - //build messages - hashed_graph::iterator it; - for( it = hG.begin(); it != hG.end(); it++) { - data_hashed_edge & e = it->second; - hashed_edge he = it->first; - - PEID peID = he.source / divisor; - m_messages[ peID ].push_back( he.source ); - m_messages[ peID ].push_back( he.target ); - m_messages[ peID ].push_back( e.weight ); - - peID = he.target / divisor; - m_messages[ peID ].push_back( he.target ); - m_messages[ peID ].push_back( he.source ); - m_messages[ peID ].push_back( e.weight ); +void parallel_contraction::redistribute_hased_graph_and_build_graph_locally( + MPI_Comm communicator, + hashed_graph& hG, + std::unordered_map& node_weights, + NodeID number_of_cnodes, + parallel_graph_access& Q) { + auto const communicator_view = mpi::communicator_view{communicator}; + auto const rank = communicator_view.rank(); + auto const size = communicator_view.size(); + auto const rank_index = static_cast(rank); + number_of_cnodes = + mpi::agree_collectively(number_of_cnodes, communicator_view, + "quotient coarse node count agreement failed"); + auto const ownership = mpi::contiguous_owner_layout{ + number_of_cnodes, static_cast(size)}; + + auto local_edges_are_valid = true; + for (auto const& [edge, data] : hG) { + static_cast(data); + local_edges_are_valid = local_edges_are_valid && + edge.source < number_of_cnodes && + edge.target < number_of_cnodes; + } + mpi::validate_collectively(local_edges_are_valid, communicator_view, + "quotient edge local validation failed"); + + auto local_weights_are_valid = true; + for (auto const& [coarse_global_id, weight] : node_weights) { + static_cast(weight); + local_weights_are_valid = + local_weights_are_valid && coarse_global_id < number_of_cnodes; + } + mpi::validate_collectively(local_weights_are_valid, communicator_view, + "quotient node-weight local validation failed"); + + auto const from = ownership.begin(rank_index); + auto const end = ownership.end(rank_index); + auto const local_num_cnodes = end - from; + auto const to = local_num_cnodes == 0 ? from : end - NodeID{1}; + mpi::validate_collectively( + std::in_range(local_num_cnodes), communicator_view, + "quotient local coarse-node count is not representable"); + + auto edge_sends = mpi::segmented_buffer{}; + auto sender_sequences_are_representable = true; + try { + auto edges_by_destination = + std::vector>( + static_cast(size)); + auto sender_sequences = + std::vector(static_cast(size), NodeID{0}); + for (auto const& [edge, data] : hG) { + auto const source_owner = ownership.owner(edge.source).value(); + auto const target_owner = ownership.owner(edge.target).value(); + for (auto const [destination, source, target] : + std::array{std::tuple{source_owner, edge.source, edge.target}, + std::tuple{target_owner, edge.target, edge.source}}) { + auto& sequence = sender_sequences.at(destination); + auto const next = contraction::checked_add(sequence, NodeID{1}); + sender_sequences_are_representable = + sender_sequences_are_representable && next.has_value(); + if (!next.has_value()) { + continue; } - - // now flood the network - for( PEID peID = 0; peID < size; peID++) { - if( peID != rank ) { - if( m_messages[peID].size() == 0 ){ - m_messages[peID].push_back(std::numeric_limits::max()); - } - - MPI_Request rq; - MPI_Isend( &m_messages[peID][0], - m_messages[peID].size(), - MPI_UNSIGNED_LONG_LONG, - peID, peID+7*size, communicator, &rq); - } + edges_by_destination.at(destination) + .push_back({source, target, data.weight, sequence}); + sequence = *next; + } + } + for (auto& edges : edges_by_destination) { + std::ranges::stable_sort(edges, {}, [](auto const& edge) { + return std::tie(edge.source, edge.target, edge.sender_sequence); + }); + } + edge_sends = + mpi::segmented_buffer::from_segments( + edges_by_destination); + } catch (...) { + mpi::abort_on_exception(communicator, "quotient edge send staging"); + } + mpi::validate_collectively(sender_sequences_are_representable, + communicator_view, + "quotient edge sender-sequence overflow"); + + auto incoming_edges = + mpi::all_to_all_v(std::move(edge_sends), communicator_view); + auto incoming_edges_are_valid = + incoming_edges.segment_count() == static_cast(size); + try { + auto const source_limit = std::min(incoming_edges.segment_count(), + static_cast(size)); + for (std::size_t source = 0; source < source_limit; ++source) { + auto source_edges = incoming_edges.segment(source); + // Preserve the pinned upstream sender-local hashed-graph order after + // the wire sort so quotient adjacency traversal remains identical. + std::ranges::sort(source_edges, {}, [](auto const& edge) { + return std::tie(edge.sender_sequence, edge.source, edge.target); + }); + for (std::size_t index = 0; index < source_edges.size(); ++index) { + auto const& edge = source_edges[index]; + auto const index_is_representable = std::in_range(index); + incoming_edges_are_valid = + incoming_edges_are_valid && index_is_representable && + (!index_is_representable || + edge.sender_sequence == static_cast(index)); + if (edge.source >= number_of_cnodes || + edge.target >= number_of_cnodes) { + incoming_edges_are_valid = false; + continue; } - - // build the local part of the graph - // - std::vector< std::vector< NodeID > > local_msg_byPE; - local_msg_byPE.resize(size); - - - if( m_messages[ rank ].size() != 0 ) { - local_msg_byPE[rank] = m_messages[rank]; + incoming_edges_are_valid = incoming_edges_are_valid && + ownership.owner(edge.source) == rank_index && + from <= edge.source && edge.source < end; + } + } + } catch (...) { + mpi::abort_on_exception(communicator, "quotient edge receive validation"); + } + mpi::validate_collectively(incoming_edges_are_valid, communicator_view, + "quotient edge received validation failed"); + + auto local_graph = hashed_graph{}; + auto received_edge_weights_are_representable = true; + try { + for (std::size_t source = 0; source < incoming_edges.segment_count(); + ++source) { + for (auto const& edge : incoming_edges.segment(source)) { + auto const key = + hashed_edge{number_of_cnodes, edge.source, edge.target}; + auto& aggregate = local_graph[key].weight; + auto const sum = contraction::checked_add(aggregate, edge.weight); + received_edge_weights_are_representable = + received_edge_weights_are_representable && sum.has_value(); + if (sum.has_value()) { + aggregate = *sum; } - - PEID counter = 0; - while( counter < size - 1) { - // wait for incomming message of an adjacent processor - MPI_Status st; - MPI_Probe(MPI_ANY_SOURCE, rank+7*size, communicator, &st); - - int message_length; - MPI_Get_count(&st, MPI_UNSIGNED_LONG_LONG, &message_length); - std::vector incmessage; incmessage.resize(message_length); - - MPI_Status rst; - MPI_Recv( &incmessage[0], message_length, MPI_UNSIGNED_LONG_LONG, st.MPI_SOURCE, rank+7*size, communicator, &rst); - counter++; - - // now integrate the changes - if( incmessage[0] == std::numeric_limits< NodeID >::max()) continue; // nothing to do - - - PEID peID = rst.MPI_SOURCE; - local_msg_byPE[peID] = incmessage; + } + } + } catch (...) { + mpi::abort_on_exception(communicator, "quotient received-edge aggregation"); + } + mpi::validate_collectively( + received_edge_weights_are_representable, communicator_view, + "quotient received edge-weight aggregation overflow"); + + auto sorted_graph = std::vector>>{}; + auto edge_counter = EdgeID{0}; + auto local_edge_count_is_representable = true; + try { + sorted_graph.resize(static_cast(local_num_cnodes)); + for (auto const& [edge, data] : local_graph) { + auto const target_is_local = from <= edge.target && edge.target < end; + auto const next = contraction::checked_local_edge_count_increment( + edge_counter, target_is_local); + local_edge_count_is_representable = + local_edge_count_is_representable && next.has_value(); + if (!next.has_value()) { + continue; + } + auto const source_index = static_cast(edge.source - from); + if (target_is_local) { + auto const target_index = static_cast(edge.target - from); + sorted_graph[target_index].emplace_back(edge.source, + data.weight / EdgeWeight{4}); + sorted_graph[source_index].emplace_back(edge.target, + data.weight / EdgeWeight{4}); + } else { + sorted_graph[source_index].emplace_back(edge.target, + data.weight / EdgeWeight{2}); + } + edge_counter = *next; + } + } catch (...) { + mpi::abort_on_exception(communicator, "quotient adjacency staging"); + } + mpi::validate_collectively(local_edge_count_is_representable, + communicator_view, + "quotient local edge-count overflow"); + + auto per_rank_edge_counts = std::vector{}; + try { + per_rank_edge_counts.resize(static_cast(size)); + } catch (...) { + mpi::abort_on_exception(communicator, "quotient global edge-count staging"); + } + mpi::check_or_abort( + MPI_Allgather(&edge_counter, 1, mpi::get_mpi_datatype(), + per_rank_edge_counts.data(), 1, + mpi::get_mpi_datatype(), communicator), + communicator, "MPI_Allgather(quotient edge counts)"); + auto const global_edge_count = + contraction::checked_sum(per_rank_edge_counts); + mpi::validate_collectively(global_edge_count.has_value(), communicator_view, + "quotient global edge-count overflow"); + auto const global_edges = *global_edge_count; + + try { + Q.start_construction(local_num_cnodes, edge_counter, number_of_cnodes, + global_edges); + Q.set_range(from, to); + auto vertex_dist = std::vector( + static_cast(size) + std::size_t{1}, NodeID{0}); + for (auto pe = std::size_t{0}; pe < vertex_dist.size(); ++pe) { + vertex_dist[pe] = ownership.boundary(pe); + } + Q.set_range_array(vertex_dist); + + for (NodeID local = 0; local < local_num_cnodes; ++local) { + auto const node = Q.new_node(); + auto const global_id = from + node; + Q.setNodeWeight(node, NodeWeight{0}); + Q.setNodeLabel(node, global_id); + auto const local_index = static_cast(local); + for (auto const& [target, weight] : sorted_graph[local_index]) { + auto const edge = Q.new_edge(node, target); + Q.setEdgeWeight(edge, weight); + } + } + Q.finish_construction(); + } catch (...) { + mpi::abort_on_exception(communicator, "quotient graph construction"); + } + + auto weight_sends = + mpi::segmented_buffer{}; + try { + auto weights_by_destination = + std::vector>( + static_cast(size)); + for (auto const& [coarse_global_id, weight] : node_weights) { + auto const destination = ownership.owner(coarse_global_id).value(); + weights_by_destination.at(destination) + .push_back({coarse_global_id, weight}); + } + for (auto& weights : weights_by_destination) { + std::ranges::stable_sort(weights, {}, [](auto const& weight) { + return std::tie(weight.coarse_global_id, weight.weight); + }); + } + weight_sends = + mpi::segmented_buffer:: + from_segments(weights_by_destination); + } catch (...) { + mpi::abort_on_exception(communicator, "quotient node-weight send staging"); + } + + auto incoming_weights = + mpi::all_to_all_v(std::move(weight_sends), communicator_view); + auto incoming_weights_are_valid = + incoming_weights.segment_count() == static_cast(size); + auto const weight_source_limit = std::min(incoming_weights.segment_count(), + static_cast(size)); + for (std::size_t source = 0; source < weight_source_limit; ++source) { + for (auto const& contribution : incoming_weights.segment(source)) { + if (contribution.coarse_global_id >= number_of_cnodes) { + incoming_weights_are_valid = false; + continue; + } + auto const local_id = Q.find_local_id(contribution.coarse_global_id); + incoming_weights_are_valid = + incoming_weights_are_valid && + ownership.owner(contribution.coarse_global_id) == rank_index && + local_id.has_value() && *local_id < local_num_cnodes; + } + } + mpi::validate_collectively(incoming_weights_are_valid, communicator_view, + "quotient node-weight received validation failed"); + + auto owned_weights = std::vector{}; + auto owned_weights_seen = std::vector{}; + auto owner_weights_are_representable = true; + try { + owned_weights.assign(static_cast(local_num_cnodes), + NodeWeight{0}); + owned_weights_seen.assign(static_cast(local_num_cnodes), + static_cast(0)); + for (std::size_t source = 0; source < incoming_weights.segment_count(); + ++source) { + for (auto const& contribution : incoming_weights.segment(source)) { + auto const local_id = Q.find_local_id(contribution.coarse_global_id); + if (!local_id.has_value() || !std::in_range(*local_id)) { + owner_weights_are_representable = false; + continue; } - - hashed_graph local_graph; - for( PEID peID = 0; peID < size; peID++) { - if(local_msg_byPE[peID].size() > 0) { - for( ULONG i = 0; i < local_msg_byPE[peID].size()-2; i+=3) { - hashed_edge he; - he.k = number_of_cnodes; - he.source = local_msg_byPE[peID][i]; - he.target = local_msg_byPE[peID][i+1]; - - local_graph[he].weight += local_msg_byPE[peID][i+2]; - }} + auto const index = static_cast(*local_id); + if (index >= owned_weights.size()) { + owner_weights_are_representable = false; + continue; } - - - ULONG from = rank * ceil(number_of_cnodes / (double)size); - ULONG to = (rank+1) * ceil(number_of_cnodes / (double)size) - 1; - // handle the case where we dont have local edges - from = std::min(from, number_of_cnodes); - to = std::min(to, number_of_cnodes - 1); - ULONG local_num_cnodes = to - from + 1; - - std::vector < std::vector< std::pair > > sorted_graph; - sorted_graph.resize( local_num_cnodes ); - - EdgeID edge_counter = 0; - for( it = local_graph.begin(); it != local_graph.end(); it++) { - data_hashed_edge & e = it->second; - hashed_edge he = it->first; - - if( from <= he.target && he.target <= to) { - std::pair< NodeID, NodeWeight > edge; - edge.first = he.target; - edge.second = e.weight/4; - - std::pair< NodeID, NodeWeight > e_bar; - e_bar.first = he.source; - e_bar.second = e.weight/4; - - sorted_graph[ he.target - from ].push_back( e_bar); - sorted_graph[ he.source - from ].push_back( edge ); - edge_counter+=2; - } else { - std::pair< NodeID, NodeWeight > edge; - edge.first = he.target; - edge.second = e.weight/2; - sorted_graph[ he.source - from ].push_back( edge ); - edge_counter++; - } - } - - ULONG global_edges = 0; - MPI_Allreduce(&edge_counter, &global_edges, 1, MPI_UNSIGNED_LONG_LONG, MPI_SUM, communicator); - - Q.start_construction(local_num_cnodes, edge_counter, number_of_cnodes, global_edges); - Q.set_range(from, to); - - std::vector< NodeID > vertex_dist( size+1, 0 ); - for( PEID peID = 0; peID <= size; peID++) { - vertex_dist[peID] = std::min(number_of_cnodes, (NodeID) (peID * ceil(number_of_cnodes / (double)size))); // from positions - } - //vertex_dist[size] = std::min(to, number_of_cnodes - 1); - Q.set_range_array(vertex_dist); - - for (NodeID i = 0; i < local_num_cnodes; ++i) { - NodeID node = Q.new_node(); - NodeID globalID = from+node; - Q.setNodeWeight(node, 0); - Q.setNodeLabel(node, globalID); - - for( EdgeID e = 0; e < sorted_graph[node].size(); e++) { - NodeID target = sorted_graph[node][e].first; - EdgeID e_bar = Q.new_edge(node, target); - Q.setEdgeWeight(e_bar, sorted_graph[node][e].second); - } - } - - Q.finish_construction(); - - for( PEID peID = 0; peID < size; peID++) { - m_messages[peID].clear(); - } - //now distribute the node weights - //pack messages - std::unordered_map< NodeID, NodeWeight >::iterator wit; - for( wit = node_weights.begin(); wit != node_weights.end(); wit++) { - NodeID node = wit->first; - NodeWeight weight = wit->second; - PEID peID = node / divisor; - - m_messages[ peID ].push_back( node ); - m_messages[ peID ].push_back( weight ); - } - - for( PEID peID = 0; peID < size; peID++) { - if( peID != rank ) { - if( m_messages[peID].size() == 0 ){ - m_messages[peID].push_back(std::numeric_limits::max()); - } - - MPI_Request rq; - MPI_Isend( &m_messages[peID][0], - m_messages[peID].size(), - MPI_UNSIGNED_LONG_LONG, - peID, peID+8*size, communicator, &rq); - } - } - - if( m_messages[ rank ].size() != 0 ) { - for( ULONG i = 0; i < m_messages[rank].size()-1; i+=2) { - NodeID globalID = m_messages[rank][i]; - NodeID node = globalID - from; - NodeWeight weight = m_messages[rank][i+1]; - Q.setNodeWeight( node , Q.getNodeWeight(node) + weight); - } - } - - counter = 0; - while( counter < size - 1) { - // wait for incomming message of an adjacent processor - MPI_Status st; - MPI_Probe(MPI_ANY_SOURCE, rank+8*size, communicator, &st); - - int message_length; - MPI_Get_count(&st, MPI_UNSIGNED_LONG_LONG, &message_length); - std::vector incmessage; incmessage.resize(message_length); - - MPI_Status rst; - MPI_Recv( &incmessage[0], message_length, MPI_UNSIGNED_LONG_LONG, st.MPI_SOURCE, rank+8*size, communicator, &rst); - counter++; - - // now integrate the changes - if( incmessage[0] == std::numeric_limits< NodeID >::max()) continue; // nothing to do - - for( ULONG i = 0; i < incmessage.size()-1; i+=2) { - NodeID globalID = incmessage[i]; - NodeWeight weight = incmessage[i+1]; - NodeID node = globalID - from; - Q.setNodeWeight( node , Q.getNodeWeight(node) + weight); - } + auto const sum = + contraction::checked_add(owned_weights[index], contribution.weight); + owner_weights_are_representable = + owner_weights_are_representable && sum.has_value(); + if (sum.has_value()) { + owned_weights[index] = *sum; + owned_weights_seen[index] = 1; } + } + } + } catch (...) { + mpi::abort_on_exception(communicator, "quotient owner node-weight staging"); + } + mpi::validate_collectively(owner_weights_are_representable, communicator_view, + "quotient owner node-weight aggregation overflow"); + mpi::validate_collectively( + std::ranges::all_of(owned_weights_seen, + [](auto seen) { return seen != 0; }), + communicator_view, "quotient owner node-weight coverage failed"); + + for (auto local = std::size_t{0}; local < owned_weights.size(); ++local) { + Q.setNodeWeight(static_cast(local), owned_weights[local]); + } } - -void parallel_contraction::update_ghost_nodes_weights( MPI_Comm communicator, parallel_graph_access & G ) { - PEID rank, size; - MPI_Comm_rank( communicator, &rank); - MPI_Comm_size( communicator, &size); - - //std::vector< std::vector< NodeID > > send_buffers; // buffers to send messages - m_send_buffers.resize(size); - std::vector< bool > PE_packed(size, false); - forall_local_nodes(G, node) { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if( !G.is_local_node(target) ) { - PEID peID = G.getTargetPE(target); - if( !PE_packed[peID] ) { // make sure a node is sent at most once - m_send_buffers[peID].push_back(G.getGlobalID(node)); - m_send_buffers[peID].push_back(G.getNodeWeight(node)); - PE_packed[peID] = true; - } - } - } endfor - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if( !G.is_local_node(target) ) { - PE_packed[G.getTargetPE(target)] = false; - } - } endfor - } endfor - - //send all neighbors their packages using Isends - //a neighbor that does not receive something gets a specific token - for( PEID peID = 0; peID < (PEID)m_send_buffers.size(); peID++) { - if( G.is_adjacent_PE(peID) ) { - //now we have to send a message - if( m_send_buffers[peID].size() == 0 ){ - // length 1 encode no message - m_send_buffers[peID].push_back(0); - } - - MPI_Request rq; - MPI_Isend( &m_send_buffers[peID][0], - m_send_buffers[peID].size(), MPI_UNSIGNED_LONG_LONG, peID, peID+9*size, communicator, &rq); - } - } - - //receive incomming - PEID counter = 0; - while( counter < G.getNumberOfAdjacentPEs()) { - // wait for incomming message of an adjacent processor - MPI_Status st; - MPI_Probe(MPI_ANY_SOURCE, rank+9*size, communicator, &st); - - int message_length; - MPI_Get_count(&st, MPI_UNSIGNED_LONG_LONG, &message_length); - std::vector message; message.resize(message_length); - - MPI_Status rst; - MPI_Recv( &message[0], message_length, MPI_UNSIGNED_LONG_LONG, st.MPI_SOURCE, rank+9*size, communicator, &rst); - counter++; - - // now integrate the changes - if(message_length == 1) continue; // nothing to do - - for( int i = 0; i < message_length-1; i+=2) { - NodeID global_id = message[i]; - NodeWeight weight = message[i+1]; - - G.setNodeWeight( G.getLocalID(global_id), weight); - } +void parallel_contraction::update_ghost_nodes_weights( + MPI_Comm communicator, + parallel_graph_access& G) { + auto const graph_communicator = mpi::communicator_view{G.getCommunicator()}; + auto communicator_is_compatible = communicator != MPI_COMM_NULL; + if (communicator_is_compatible) { + auto comparison = int{MPI_UNEQUAL}; + mpi::check_or_abort( + MPI_Comm_compare(communicator, G.getCommunicator(), &comparison), + G.getCommunicator(), "MPI_Comm_compare(contraction ghost weights)"); + communicator_is_compatible = + comparison == MPI_IDENT || comparison == MPI_CONGRUENT; + } + mpi::validate_collectively( + communicator_is_compatible, graph_communicator, + "contraction ghost-weight communicator validation failed"); + auto const global_node_count = mpi::agree_collectively( + G.number_of_global_nodes(), graph_communicator, + "contraction ghost-weight global count agreement failed"); + + auto const& plan = G.ghost_plan(); + auto outgoing = std::vector>{}; + auto send_buffer = mpi::segmented_buffer{}; + auto outgoing_is_valid = true; + try { + outgoing.resize(plan.topology().destinations().size()); + for (auto destination_index = std::size_t{0}; + destination_index < plan.topology().destinations().size(); + ++destination_index) { + auto const local_nodes = plan.outgoing_local_nodes(destination_index); + auto& records = outgoing[destination_index]; + records.reserve(local_nodes.size()); + auto previous = std::optional{}; + for (auto const local : local_nodes) { + auto const local_is_valid = + local < G.number_of_local_nodes() && + (!previous.has_value() || *previous < local); + outgoing_is_valid = outgoing_is_valid && local_is_valid; + if (!local_is_valid) { + continue; } - + auto const global_id = G.getGlobalID(local); + outgoing_is_valid = outgoing_is_valid && G.is_interface_node(local) && + global_id < global_node_count && + G.find_local_id(global_id) == local; + records.push_back({global_id, G.getNodeWeight(local)}); + previous = local; + } + } + send_buffer = + mpi::segmented_buffer::from_segments( + outgoing); + } catch (...) { + mpi::abort_on_exception(plan.topology().native_handle(), + "contraction ghost-weight send staging"); + } + if (!mpi::detail::collective_predicate(outgoing_is_valid, + plan.topology().view())) { + mpi::throw_collectively_agreed_semantic_error( + plan.topology().native_handle(), + "contraction ghost-weight outgoing validation failed"); + } + + auto received = + mpi::neighbor_all_to_all_v(std::move(send_buffer), plan.topology()); + using pending_weight = std::tuple; + auto pending = std::vector{}; + auto received_is_valid = + received.segment_count() == plan.topology().sources().size(); + try { + auto const source_limit = + std::min(received.segment_count(), plan.topology().sources().size()); + for (auto source_index = std::size_t{0}; source_index < source_limit; + ++source_index) { + auto const source = plan.topology().sources()[source_index]; + auto const records = received.segment(source_index); + auto const expected = plan.expected_ghost_nodes(source_index); + auto received_ids = std::vector{}; + received_ids.reserve(records.size()); + pending.reserve(pending.size() + records.size()); + for (auto const& record : records) { + received_ids.push_back(record.global_id); + auto const local_id = G.find_ghost_local_id(record.global_id, source); + received_is_valid = received_is_valid && + record.global_id < global_node_count && + local_id.has_value(); + pending.emplace_back(record.global_id, source, + local_id.value_or(NodeID{0}), record.weight); + } + std::ranges::sort(received_ids); + received_is_valid = + received_is_valid && records.size() == expected.size() && + std::ranges::adjacent_find(received_ids) == received_ids.end() && + std::ranges::equal(received_ids, expected); + } + std::ranges::sort(pending, {}, [](auto const& update) { + return std::tie(std::get<0>(update), std::get<1>(update)); + }); + received_is_valid = + received_is_valid && std::ranges::adjacent_find( + pending, [](auto const& lhs, auto const& rhs) { + return std::get<0>(lhs) == std::get<0>(rhs); + }) == pending.end(); + } catch (...) { + mpi::abort_on_exception(plan.topology().native_handle(), + "contraction ghost-weight receive staging"); + } + if (!mpi::detail::collective_predicate(received_is_valid, + plan.topology().view())) { + mpi::throw_collectively_agreed_semantic_error( + plan.topology().native_handle(), + "contraction ghost-weight received validation failed"); + } + + for (auto const& [global_id, source, local_id, weight] : pending) { + static_cast(global_id); + static_cast(source); + G.setNodeWeight(local_id, weight); + } +} } diff --git a/parallel/parallel_src/lib/parallel_contraction_projection/parallel_contraction.h b/parallel/parallel_src/lib/parallel_contraction_projection/parallel_contraction.h index 9d5da7ac..6d14b915 100644 --- a/parallel/parallel_src/lib/parallel_contraction_projection/parallel_contraction.h +++ b/parallel/parallel_src/lib/parallel_contraction_projection/parallel_contraction.h @@ -8,43 +8,184 @@ #ifndef PARALLEL_CONTRACTION_64O127GD #define PARALLEL_CONTRACTION_64O127GD +#include +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_tools.h" #include "data_structure/hashed_graph.h" #include "data_structure/parallel_graph_access.h" #include "partition_config.h" +namespace parhip { +struct parallel_contraction_test_access; class parallel_contraction { public: - parallel_contraction(); - virtual ~parallel_contraction(); + void contract_to_distributed_quotient(MPI_Comm communicator, + PPartitionConfig& config, + parallel_graph_access& G, + parallel_graph_access& Q); - void contract_to_distributed_quotient( MPI_Comm communicator, PPartitionConfig & config, - parallel_graph_access & G, - parallel_graph_access & Q); private: - // compute mapping of labels id into contiguous intervall [0, ...., num_lables) - void compute_label_mapping( MPI_Comm communicator, parallel_graph_access & G, - NodeID & global_num_distinct_ids, - std::unordered_map< NodeID, NodeID > & label_mapping); + friend struct parallel_contraction_test_access; + + // compute mapping of labels id into contiguous intervall [0,...,num_lables) + void compute_label_mapping(MPI_Comm communicator, + parallel_graph_access& G, + NodeID& global_num_distinct_ids, + std::unordered_map& label_mapping); + + void get_nodes_to_cnodes_ghost_nodes( + MPI_Comm communicator, + parallel_graph_access& G, + NodeID number_of_distinct_labels, + std::unordered_map const& label_mapping); + + void build_quotient_graph_locally( + MPI_Comm communicator, + parallel_graph_access& G, + NodeID number_of_distinct_labels, + hashed_graph& hG, + std::unordered_map& node_weights); + + void redistribute_hased_graph_and_build_graph_locally( + MPI_Comm communicator, + hashed_graph& hG, + std::unordered_map& node_weights, + NodeID number_of_cnodes, + parallel_graph_access& Q); + + void update_ghost_nodes_weights(MPI_Comm communicator, + parallel_graph_access& G); +}; - void get_nodes_to_cnodes_ghost_nodes( MPI_Comm communicator, parallel_graph_access & G ); +// Comm types +namespace contraction { +struct label_request { + NodeID old_label; +}; - void build_quotient_graph_locally( parallel_graph_access & G, - NodeID number_of_distinct_labels, - hashed_graph & hG, - std::unordered_map< NodeID, NodeWeight > & node_weights); +struct label_reply { + NodeID old_label; + NodeID coarse_global_id; +}; - void redistribute_hased_graph_and_build_graph_locally( MPI_Comm communicator, hashed_graph & hG, - std::unordered_map< NodeID, NodeWeight > & node_weights, - NodeID number_of_cnodes, - parallel_graph_access & Q); +struct bundled_edge { + NodeID source; + NodeID target; + EdgeWeight weight; + NodeID sender_sequence; +}; - void update_ghost_nodes_weights( MPI_Comm communicator, parallel_graph_access & G ); +struct node_weight_contribution { + NodeID coarse_global_id; + NodeWeight weight; +}; - // some send buffers - std::vector< std::vector< NodeID > > m_messages; - std::vector< std::vector< NodeID > > m_out_messages; - std::vector< std::vector< NodeID > > m_send_buffers; // buffers to send messages +struct ghost_cnode_assignment { + NodeID global_id; + NodeID coarse_global_id; }; +struct ghost_node_weight { + NodeID global_id; + NodeWeight weight; +}; + +template +[[nodiscard]] constexpr auto checked_add(T lhs, T rhs) noexcept + -> std::optional { + if (rhs > std::numeric_limits::max() - lhs) { + return std::nullopt; + } + return lhs + rhs; +} + +[[nodiscard]] constexpr auto checked_local_edge_count_increment( + EdgeID count, + bool target_is_local) noexcept -> std::optional { + return checked_add(count, target_is_local ? EdgeID{2} : EdgeID{1}); +} +template + requires std:: + same_as>, T> + [[nodiscard]] constexpr auto checked_sum(Range&& values) + -> std::optional { + auto sum = T{0}; + for (auto const value : values) { + auto const next = checked_add(sum, value); + if (!next.has_value()) { + return std::nullopt; + } + sum = *next; + } + return sum; +} +} // namespace contraction + +static_assert(std::is_standard_layout_v); +static_assert(std::is_trivially_copyable_v); +static_assert(std::is_standard_layout_v); +static_assert(std::is_trivially_copyable_v); +static_assert(std::is_standard_layout_v); +static_assert(std::is_trivially_copyable_v); +static_assert( + std::is_standard_layout_v); +static_assert( + std::is_trivially_copyable_v); +static_assert(std::is_standard_layout_v); +static_assert( + std::is_trivially_copyable_v); +static_assert(std::is_standard_layout_v); +static_assert(std::is_trivially_copyable_v); +} + +template <> +struct parhip::mpi::wire_members { + inline static constexpr auto value = + std::tuple{&parhip::contraction::label_request::old_label}; +}; + +template <> +struct parhip::mpi::wire_members { + inline static constexpr auto value = std::tuple{ + &parhip::contraction::label_reply::old_label, + &parhip::contraction::label_reply::coarse_global_id}; +}; + +template <> +struct parhip::mpi::wire_members { + inline static constexpr auto value = std::tuple{ + &parhip::contraction::bundled_edge::source, + &parhip::contraction::bundled_edge::target, + &parhip::contraction::bundled_edge::weight, + &parhip::contraction::bundled_edge::sender_sequence}; +}; + +template <> +struct parhip::mpi::wire_members< + parhip::contraction::node_weight_contribution> { + inline static constexpr auto value = std::tuple{ + &parhip::contraction::node_weight_contribution::coarse_global_id, + &parhip::contraction::node_weight_contribution::weight}; +}; + +template <> +struct parhip::mpi::wire_members { + inline static constexpr auto value = std::tuple{ + &parhip::contraction::ghost_cnode_assignment::global_id, + &parhip::contraction::ghost_cnode_assignment::coarse_global_id}; +}; + +template <> +struct parhip::mpi::wire_members { + inline static constexpr auto value = std::tuple{ + &parhip::contraction::ghost_node_weight::global_id, + &parhip::contraction::ghost_node_weight::weight}; +}; #endif /* end of include guard: PARALLEL_CONTRACTION_64O127GD */ diff --git a/parallel/parallel_src/lib/parallel_contraction_projection/parallel_projection.cpp b/parallel/parallel_src/lib/parallel_contraction_projection/parallel_projection.cpp index 70032512..784d5b0a 100644 --- a/parallel/parallel_src/lib/parallel_contraction_projection/parallel_projection.cpp +++ b/parallel/parallel_src/lib/parallel_contraction_projection/parallel_projection.cpp @@ -7,6 +7,17 @@ #include "parallel_projection.h" +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_adapter.h" +#include "communication/contiguous_owner_layout.h" +#include "communication/mpi_trace.h" +namespace parhip { parallel_projection::parallel_projection() { } @@ -15,137 +26,217 @@ parallel_projection::~parallel_projection() { } -//issue recv before send void parallel_projection::parallel_project( MPI_Comm communicator, parallel_graph_access & finer, parallel_graph_access & coarser ) { - PEID rank, size; - MPI_Comm_rank( communicator, &rank); - MPI_Comm_size( communicator, &size); - - NodeID divisor = ceil(coarser.number_of_global_nodes() / (double)size); - - m_messages.resize(size); - - std::unordered_map< NodeID, std::vector< NodeID > > cnode_to_nodes; - forall_local_nodes(finer, node) { - NodeID cnode = finer.getCNode(node); - //std::cout << "cnode " << cnode << std::endl; - if( coarser.is_local_node_from_global_id(cnode) ) { - NodeID new_label = coarser.getNodeLabel(coarser.getLocalID(cnode)); - finer.setNodeLabel(node, new_label); - } else { - //we have to request it from another PE - PEID peID = cnode / divisor; // cnode is - - if( cnode_to_nodes.find( cnode ) == cnode_to_nodes.end()) { - m_messages[peID].push_back(cnode); // we are requesting the label of this node - } - - cnode_to_nodes[cnode].push_back(node); - } - } endfor - - for( PEID peID = 0; peID < size; peID++) { - if( peID != rank ) { - if( m_messages[peID].size() == 0 ){ - m_messages[peID].push_back(std::numeric_limits::max()); - } - - MPI_Request rq; - MPI_Isend( &m_messages[peID][0], - m_messages[peID].size(), - MPI_UNSIGNED_LONG_LONG, - peID, peID+size, communicator, &rq); - } - } - - std::vector< std::vector< NodeID > > out_messages; - out_messages.resize(size); - - PEID counter = 0; - while( counter < size - 1) { - // wait for incomming message of an adjacent processor - MPI_Status st; - MPI_Probe(MPI_ANY_SOURCE, rank+size, communicator, &st); - - int message_length; - MPI_Get_count(&st, MPI_UNSIGNED_LONG_LONG, &message_length); - std::vector incmessage; incmessage.resize(message_length); - - MPI_Status rst; - MPI_Recv( &incmessage[0], message_length, MPI_UNSIGNED_LONG_LONG, st.MPI_SOURCE, rank+size, communicator, &rst); - counter++; - - PEID peID = st.MPI_SOURCE; - // now integrate the changes - if( incmessage[0] == std::numeric_limits< NodeID >::max()) { - out_messages[peID].push_back(std::numeric_limits< NodeID >::max()); - MPI_Request rq; - MPI_Isend( &out_messages[peID][0], - out_messages[peID].size(), - MPI_UNSIGNED_LONG_LONG, - peID, peID+2*size, communicator, &rq); - - continue; // nothing to do - } - - - for( int i = 0; i < message_length; i++) { - NodeID cnode = coarser.getLocalID(incmessage[i]); - out_messages[peID].push_back(coarser.getNodeLabel(cnode)); - } - - MPI_Request rq; - MPI_Isend( &out_messages[peID][0], - out_messages[peID].size(), - MPI_UNSIGNED_LONG_LONG, - peID, peID+2*size, communicator, &rq); - - } - - counter = 0; - while( counter < size - 1) { - // wait for incomming message of an adjacent processor - MPI_Status st; ULONG tag = rank+2*size; - MPI_Probe(MPI_ANY_SOURCE, tag, communicator, &st); - - int message_length; - MPI_Get_count(&st, MPI_UNSIGNED_LONG_LONG, &message_length); - std::vector incmessage; incmessage.resize(message_length); - - MPI_Status rst; - MPI_Recv( &incmessage[0], message_length, MPI_UNSIGNED_LONG_LONG, st.MPI_SOURCE, tag, communicator, &rst); - counter++; - - // now integrate the changes - if( incmessage[0] == std::numeric_limits< NodeID >::max()) { - continue; // nothing to do - } - - PEID peID = st.MPI_SOURCE; - for( ULONG i = 0; i < (ULONG)incmessage.size(); i++) { - std::vector< NodeID > & proj = cnode_to_nodes[m_messages[peID][i]]; - NodeID label = incmessage[i]; - - for( ULONG j = 0; j < proj.size(); j++) { - finer.setNodeLabel(proj[j], label); - } - } - } - - finer.update_ghost_node_data_global(); // blocking + struct pending_label_update { + NodeID node; + NodeID label; + }; + + auto const communicator_view = mpi::communicator_view{communicator}; + auto const rank = communicator_view.rank(); + auto const size = communicator_view.size(); + auto const rank_index = static_cast(rank); + auto const number_of_coarse_nodes = mpi::agree_collectively( + coarser.number_of_global_nodes(), + communicator_view, + "projection coarse node count agreement failed"); + auto const ownership = mpi::contiguous_owner_layout{ + number_of_coarse_nodes, static_cast(size)}; + auto const coarse_begin = ownership.begin(rank_index); + auto const coarse_end = ownership.end(rank_index); + auto const expected_local_coarse_nodes = coarse_end - coarse_begin; + auto const expected_last_coarse_node = + expected_local_coarse_nodes == 0 ? coarse_begin : coarse_end - 1; + + auto local_coarse_nodes_are_valid = + coarser.number_of_local_nodes() == expected_local_coarse_nodes && + coarser.get_from_range() == coarse_begin && + coarser.get_to_range() == expected_last_coarse_node; + forall_local_nodes(finer, node) { + auto const coarse_global_id = finer.getCNode(node); + auto const owner = ownership.owner(coarse_global_id); + if (!owner.has_value()) { + local_coarse_nodes_are_valid = false; + continue; + } + if (*owner == rank_index && + !coarser.is_local_node_from_global_id(coarse_global_id)) { + local_coarse_nodes_are_valid = false; + } + } endfor + mpi::validate_collectively( + local_coarse_nodes_are_valid, + communicator_view, + "projection local coarse-node validation failed"); + + std::vector pending_updates; + pending_updates.reserve( + static_cast(finer.number_of_local_nodes())); + auto requests_by_destination = + std::vector>( + static_cast(size)); + std::map request_by_coarse_node; + std::unordered_map> nodes_by_request; + std::unordered_map coarse_node_by_request; + + forall_local_nodes(finer, node) { + auto const cnode = finer.getCNode(node); + auto const owner = ownership.owner(cnode).value(); + if (owner == rank_index) { + auto const new_label = coarser.getNodeLabel(coarser.getLocalID(cnode)); + pending_updates.push_back({node, new_label}); + } else { + auto [position, inserted] = request_by_coarse_node.try_emplace( + cnode, + projection::request{finer.getGlobalID(node), cnode}); + auto const request_id = position->second.request_id; + if (inserted) { + requests_by_destination.at(owner).push_back(position->second); + coarse_node_by_request.emplace(request_id, cnode); + } + nodes_by_request[request_id].push_back(node); + } + } endfor + + for (std::size_t destination = 0; + destination < requests_by_destination.size(); + ++destination) { + auto& destination_requests = requests_by_destination[destination]; + std::ranges::stable_sort(destination_requests, {}, [](auto const& request) { + return std::tie(request.coarse_global_id, request.request_id); + }); + } + + auto incoming_requests = mpi::all_to_all_v( + mpi::segmented_buffer::from_segments( + requests_by_destination), + mpi::communicator_view{communicator}); + auto replies_by_destination = std::vector>( + static_cast(size)); + auto incoming_requests_are_valid = true; + for (std::size_t source = 0; source < incoming_requests.segment_count(); + ++source) { + auto seen_request_ids = std::unordered_set{}; + seen_request_ids.reserve(incoming_requests.segment(source).size()); + auto seen_coarse_ids = std::unordered_set{}; + seen_coarse_ids.reserve(incoming_requests.segment(source).size()); + for (auto const& request : incoming_requests.segment(source)) { + auto const owner = ownership.owner(request.coarse_global_id); + if (!owner.has_value()) { + incoming_requests_are_valid = false; + continue; + } + if (*owner != rank_index || + !coarser.is_local_node_from_global_id(request.coarse_global_id) || + !seen_request_ids.insert(request.request_id).second || + !seen_coarse_ids.insert(request.coarse_global_id).second) { + incoming_requests_are_valid = false; + } + } + } + mpi::validate_collectively( + incoming_requests_are_valid, + communicator_view, + "projection request received validation failed"); + + for (std::size_t source = 0; source < incoming_requests.segment_count(); + ++source) { + auto& replies = replies_by_destination[source]; + for (auto const& request : incoming_requests.segment(source)) { + replies.push_back(projection::reply{ + request.request_id, + request.coarse_global_id, + coarser.getNodeLabel( + coarser.getLocalID(request.coarse_global_id))}); + } + std::ranges::stable_sort(replies, {}, [](auto const& reply) { + return std::tie(reply.request_id, reply.coarse_global_id); + }); + } + + auto incoming_replies = mpi::all_to_all_v( + mpi::segmented_buffer::from_segments( + replies_by_destination), + mpi::communicator_view{communicator}); + + auto incoming_replies_are_valid = true; + auto received_request_ids = std::unordered_set{}; + received_request_ids.reserve(coarse_node_by_request.size()); + for (std::size_t source = 0; source < incoming_replies.segment_count(); + ++source) { + for (auto const& reply : incoming_replies.segment(source)) { + auto const owner = ownership.owner(reply.coarse_global_id); + auto const coarse_node = coarse_node_by_request.find(reply.request_id); + auto const projected_nodes = nodes_by_request.find(reply.request_id); + if (!owner.has_value() || *owner != source || + coarse_node == coarse_node_by_request.end() || + projected_nodes == nodes_by_request.end() || + coarse_node->second != reply.coarse_global_id || + !received_request_ids.insert(reply.request_id).second) { + incoming_replies_are_valid = false; + } + } + } + incoming_replies_are_valid = + incoming_replies_are_valid && + received_request_ids.size() == coarse_node_by_request.size() && + std::ranges::all_of(coarse_node_by_request, [&](auto const& entry) { + return received_request_ids.contains(entry.first); + }); + mpi::validate_collectively( + incoming_replies_are_valid, + communicator_view, + "projection reply received validation failed"); + + for (std::size_t destination = 0; + destination < requests_by_destination.size(); + ++destination) { + for (auto const& request : requests_by_destination[destination]) { + KAHIP_MPI_TRACE(mpi::trace::projection_request( + mpi::trace::current_hierarchy(), request.request_id, + rank, + static_cast(destination), + request.coarse_global_id)); + } + } + + for (std::size_t source = 0; source < replies_by_destination.size(); + ++source) { + for (auto const& reply : replies_by_destination[source]) { + KAHIP_MPI_TRACE(mpi::trace::projection_reply( + mpi::trace::current_hierarchy(), reply.request_id, + static_cast(source), + rank, + reply.coarse_global_id, + reply.label)); + } + } + + for (auto const& reply : incoming_replies.storage()) { + auto const& projected_nodes = nodes_by_request.at(reply.request_id); + for (auto const node : projected_nodes) { + pending_updates.push_back({node, reply.label}); + } + } + for (auto const& [node, label] : pending_updates) { + finer.setNodeLabel(node, label); + } + + finer.update_ghost_node_data_global(); // blocking } //initial assignment after initial partitioning void parallel_projection::initial_assignment( parallel_graph_access & G, complete_graph_access & Q) { - forall_local_nodes(G, node) { - G.setNodeLabel(node, Q.getNodeLabel(G.getGlobalID(node))); - if( G.is_interface_node(node) ) { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if( !G.is_local_node( target ) ) { - G.setNodeLabel(target, Q.getNodeLabel(G.getGlobalID(target))); - } - } endfor - } - } endfor + forall_local_nodes(G, node) { + G.setNodeLabel(node, Q.getNodeLabel(G.getGlobalID(node))); + if( G.is_interface_node(node) ) { + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + if( !G.is_local_node( target ) ) { + G.setNodeLabel(target, Q.getNodeLabel(G.getGlobalID(target))); + } + } endfor +} + } endfor +} } diff --git a/parallel/parallel_src/lib/parallel_contraction_projection/parallel_projection.h b/parallel/parallel_src/lib/parallel_contraction_projection/parallel_projection.h index 6266c523..2fe71df3 100644 --- a/parallel/parallel_src/lib/parallel_contraction_projection/parallel_projection.h +++ b/parallel/parallel_src/lib/parallel_contraction_projection/parallel_projection.h @@ -8,7 +8,23 @@ #ifndef PARALLEL_PROJECTION_HBRCPQ0P #define PARALLEL_PROJECTION_HBRCPQ0P +#include + +#include "communication/mpi_types.h" #include "data_structure/parallel_graph_access.h" +namespace parhip { +namespace projection { +struct request { + NodeID request_id; + NodeID coarse_global_id; +}; + +struct reply { + NodeID request_id; + NodeID coarse_global_id; + NodeID label; +}; +} // namespace projection class parallel_projection { public: @@ -19,11 +35,23 @@ class parallel_projection { //initial assignment after initial partitioning void initial_assignment( parallel_graph_access & G, complete_graph_access & Q); -private: - std::vector< std::vector< NodeID > > m_messages; }; +} +template <> +struct parhip::mpi::wire_members { + inline static constexpr auto value = std::tuple{ + &parhip::projection::request::request_id, + &parhip::projection::request::coarse_global_id}; +}; +template <> +struct parhip::mpi::wire_members { + inline static constexpr auto value = std::tuple{ + &parhip::projection::reply::request_id, + &parhip::projection::reply::coarse_global_id, + &parhip::projection::reply::label}; +}; #endif /* end of include guard: PARALLEL_PROJECTION_HBRCPQ0P */ diff --git a/parallel/parallel_src/lib/parallel_label_compress/hmap_wrapper.h b/parallel/parallel_src/lib/parallel_label_compress/hmap_wrapper.h index df2dbd98..0e063c80 100644 --- a/parallel/parallel_src/lib/parallel_label_compress/hmap_wrapper.h +++ b/parallel/parallel_src/lib/parallel_label_compress/hmap_wrapper.h @@ -9,95 +9,95 @@ #define HMAP_WRAPPER_RQFK3ARC #include "data_structure/linear_probing_hashmap.h" - +namespace parhip { template class hmap_wrapper { - public: +public: - hmap_wrapper(PPartitionConfig & config) { - m_config = config; - }; + hmap_wrapper(PPartitionConfig & config) { + m_config = config; + }; - virtual ~hmap_wrapper() {}; + virtual ~hmap_wrapper() {}; - void init( NodeID max_fill_count ); - void clear(); - NodeWeight & operator[](NodeID node); + void init( NodeID max_fill_count ); + void clear(); + NodeWeight & operator[](NodeID node); private: - T mapping_type; - PPartitionConfig m_config; + T mapping_type; + PPartitionConfig m_config; }; template <> class hmap_wrapper < linear_probing_hashmap > { - public: +public: - hmap_wrapper(PPartitionConfig & config) { - m_config = config; - }; + hmap_wrapper(PPartitionConfig & config) { + m_config = config; + }; - virtual ~hmap_wrapper() {}; + virtual ~hmap_wrapper() {}; - void init(NodeID max_fill_count ) {mapping_type.init(max_fill_count, m_config.ht_fill_factor);}; - void clear() { mapping_type.clear(); }; - NodeWeight & operator[](NodeID node) {return mapping_type[node];}; + void init(NodeID max_fill_count ) {mapping_type.init(max_fill_count, m_config.ht_fill_factor);}; + void clear() { mapping_type.clear(); }; + NodeWeight & operator[](NodeID node) {return mapping_type[node];}; - private: - linear_probing_hashmap mapping_type; - PPartitionConfig m_config; +private: + linear_probing_hashmap mapping_type; + PPartitionConfig m_config; }; template <> class hmap_wrapper > { - public: +public: - hmap_wrapper(PPartitionConfig & config) { - m_config = config; - }; + hmap_wrapper(PPartitionConfig & config) { + m_config = config; + }; - virtual ~hmap_wrapper() {}; + virtual ~hmap_wrapper() {}; - void init( NodeID max_fill_count ) {}; - void clear() { mapping_type.clear(); }; - NodeWeight & operator[](NodeID node) {return mapping_type[node];}; + void init( NodeID max_fill_count ) {}; + void clear() { mapping_type.clear(); }; + NodeWeight & operator[](NodeID node) {return mapping_type[node];}; - private: - std::unordered_map mapping_type; - PPartitionConfig m_config; +private: + std::unordered_map mapping_type; + PPartitionConfig m_config; }; template <> class hmap_wrapper > { - public: +public: - hmap_wrapper(PPartitionConfig & config) { - m_config = config; - }; + hmap_wrapper(PPartitionConfig & config) { + m_config = config; + }; - virtual ~hmap_wrapper() {}; + virtual ~hmap_wrapper() {}; - void init( NodeID max_fill ) { - mapping_type.resize(m_config.k); - for( ULONG k = 0; k < m_config.k; k++) { - mapping_type[k] = 0; - } + void init( NodeID max_fill ) { + mapping_type.resize(m_config.k); + for( ULONG k = 0; k < m_config.k; k++) { + mapping_type[k] = 0; + } - }; + }; - void clear() { - for( ULONG k = 0; k < m_config.k; k++) { - mapping_type[k] = 0; - } - }; + void clear() { + for( ULONG k = 0; k < m_config.k; k++) { + mapping_type[k] = 0; + } + }; - NodeWeight & operator[](NodeID node) {return mapping_type[node];}; + NodeWeight & operator[](NodeID node) {return mapping_type[node];}; - private: - std::vector mapping_type; - PPartitionConfig m_config; +private: + std::vector mapping_type; + PPartitionConfig m_config; }; - +} #endif /* end of include guard: HMAP_WRAPPER_RQFK3ARC */ diff --git a/parallel/parallel_src/lib/parallel_label_compress/node_ordering.cpp b/parallel/parallel_src/lib/parallel_label_compress/node_ordering.cpp index f4be6241..1aa66200 100644 --- a/parallel/parallel_src/lib/parallel_label_compress/node_ordering.cpp +++ b/parallel/parallel_src/lib/parallel_label_compress/node_ordering.cpp @@ -6,7 +6,7 @@ *****************************************************************************/ #include "node_ordering.h" - +namespace parhip { node_ordering::node_ordering() { } @@ -14,4 +14,4 @@ node_ordering::node_ordering() { node_ordering::~node_ordering() { } - +} diff --git a/parallel/parallel_src/lib/parallel_label_compress/node_ordering.h b/parallel/parallel_src/lib/parallel_label_compress/node_ordering.h index 6d0f6588..65541bc4 100644 --- a/parallel/parallel_src/lib/parallel_label_compress/node_ordering.h +++ b/parallel/parallel_src/lib/parallel_label_compress/node_ordering.h @@ -14,7 +14,7 @@ #include "partition_config.h" #include "data_structure/parallel_graph_access.h" #include "tools/random_functions.h" - +namespace parhip { class node_ordering { public: node_ordering(); @@ -26,46 +26,48 @@ class node_ordering { } endfor switch( config.node_ordering ) { - case RANDOM_NODEORDERING: + case NodeOrderingType::RANDOM_NODEORDERING: order_nodes_random(config, G, ordered_nodes); - break; - case DEGREE_NODEORDERING: + break; + case NodeOrderingType::DEGREE_NODEORDERING: order_nodes_degree(config, G, ordered_nodes); - break; - case LEASTGHOSTNODESFIRST_DEGREE_NODEODERING: + break; + case NodeOrderingType:: + LEASTGHOSTNODESFIRST_DEGREE_NODEODERING: order_leastghostnodes_nodes_degree(config, G, ordered_nodes); - break; - case DEGREE_LEASTGHOSTNODESFIRST_NODEODERING: + break; + case NodeOrderingType:: + DEGREE_LEASTGHOSTNODESFIRST_NODEODERING: order_nodes_degree_leastghostnodes(config, G, ordered_nodes); - break; - } + break; + } } - void order_nodes_random(const PPartitionConfig & config, parallel_graph_access & G, std::vector< NodeID > & ordered_nodes) { + void order_nodes_random(const PPartitionConfig & config, parallel_graph_access & G, std::vector< NodeID > & ordered_nodes) { random_functions::permutate_vector_fast(ordered_nodes, false); } - void order_nodes_degree(const PPartitionConfig & config, parallel_graph_access & G, std::vector< NodeID > & ordered_nodes) { - std::sort( ordered_nodes.begin(), ordered_nodes.end(), + void order_nodes_degree(const PPartitionConfig & config, parallel_graph_access & G, std::vector< NodeID > & ordered_nodes) { + std::sort( ordered_nodes.begin(), ordered_nodes.end(), [&]( const NodeID & lhs, const NodeID & rhs) -> bool { return (G.getNodeDegree(lhs) < G.getNodeDegree(rhs)); }); } - void order_leastghostnodes_nodes_degree(const PPartitionConfig & config, parallel_graph_access & G, std::vector< NodeID > & ordered_nodes) { - std::sort( ordered_nodes.begin(), ordered_nodes.end(), + void order_leastghostnodes_nodes_degree(const PPartitionConfig & config, parallel_graph_access & G, std::vector< NodeID > & ordered_nodes) { + std::sort( ordered_nodes.begin(), ordered_nodes.end(), [&]( const NodeID & lhs, const NodeID & rhs) -> bool { return (G.getNodeDegree(lhs) < G.getNodeDegree(rhs)); }); } - void order_nodes_degree_leastghostnodes(const PPartitionConfig & config, parallel_graph_access & G, std::vector< NodeID > & ordered_nodes) { - std::sort( ordered_nodes.begin(), ordered_nodes.end(), + void order_nodes_degree_leastghostnodes(const PPartitionConfig & config, parallel_graph_access & G, std::vector< NodeID > & ordered_nodes) { + std::sort( ordered_nodes.begin(), ordered_nodes.end(), [&]( const NodeID & lhs, const NodeID & rhs) -> bool { - return (G.getNodeDegree(lhs) < G.getNodeDegree(rhs)); + return (G.getNodeDegree(lhs) < G.getNodeDegree(rhs)); }); } }; - +} #endif /* end of include guard: NODE_ORDERING_HM1YMLB1 */ diff --git a/parallel/parallel_src/lib/parallel_label_compress/parallel_label_compress.h b/parallel/parallel_src/lib/parallel_label_compress/parallel_label_compress.h index 28fd7873..4b25f434 100644 --- a/parallel/parallel_src/lib/parallel_label_compress/parallel_label_compress.h +++ b/parallel/parallel_src/lib/parallel_label_compress/parallel_label_compress.h @@ -14,99 +14,100 @@ #include "hmap_wrapper.h" #include "node_ordering.h" - -template +namespace parhip { +template class parallel_label_compress { - public: - parallel_label_compress() {}; - virtual ~parallel_label_compress() {}; - - void perform_parallel_label_compression( PPartitionConfig & config, - parallel_graph_access & G, bool balance, bool for_coarsening = true) { - - if( config.label_iterations == 0) return; - NodeWeight cluster_upperbound = config.upper_bound_cluster; - - std::vector< NodeID > permutation( G.number_of_local_nodes() ); - if( for_coarsening ) { - node_ordering no; - no.order_nodes( config, G, permutation); - } else { - random_functions::permutate_vector_fast( permutation, true); - } - - //std::unordered_map hash_map; - hmap_wrapper< T > hash_map(config); - hash_map.init( G.get_max_degree() ); - for( ULONG i = 0; i < config.label_iterations; i++) { - NodeID prev_node = 0; - forall_local_nodes(G, rnode) { - NodeID node = permutation[rnode]; // use the current random node - - //move the node to the cluster that is most common in the neighborhood - //second sweep for finding max and resetting array - PartitionID max_block = G.getNodeLabel(node); - PartitionID old_block = G.getNodeLabel(node); - PartitionID max_value = 0; - NodeWeight node_weight = G.getNodeWeight(node); - bool own_block_balanced = G.getBlockSize(old_block) <= cluster_upperbound || !balance; - - if( G.getNodeDegree(node) == 0) { - // find a block to assign it to - if(config.vcycle) { - NodeWeight prev_block_size = G.getBlockSize( G.getNodeLabel( prev_node ) ); - bool same_block = G.getSecondPartitionIndex(prev_node)==G.getSecondPartitionIndex(node); - if( prev_block_size + node_weight <= cluster_upperbound && same_block ) { - max_block = G.getNodeLabel( prev_node ); - } - } else { - NodeWeight prev_block_size = G.getBlockSize( G.getNodeLabel( prev_node ) ); - if( prev_block_size + node_weight <= cluster_upperbound) { - max_block = G.getNodeLabel( prev_node ); - } - } +public: + parallel_label_compress() {}; + virtual ~parallel_label_compress() {}; + + void perform_parallel_label_compression( PPartitionConfig & config, + parallel_graph_access & G, bool balance, bool for_coarsening = true) { + + if( config.label_iterations == 0) return; + NodeWeight cluster_upperbound = config.upper_bound_cluster; + + std::vector< NodeID > permutation( G.number_of_local_nodes() ); + if( for_coarsening ) { + node_ordering no; + no.order_nodes( config, G, permutation); + } else { + random_functions::permutate_vector_fast( permutation, true); + } + //std::unordered_map hash_map; + hmap_wrapper< T > hash_map(config); + hash_map.init( G.get_max_degree() ); + for( ULONG i = 0; i < config.label_iterations; i++) { + KAHIP_MPI_TRACE_SET_ITERATION(i); + NodeID prev_node = 0; + forall_local_nodes(G, rnode) { + NodeID node = permutation[rnode]; // use the current random node + + //move the node to the cluster that is most common in the neighborhood + //second sweep for finding max and resetting array + PartitionID max_block = G.getNodeLabel(node); + PartitionID old_block = G.getNodeLabel(node); + PartitionID max_value = 0; + NodeWeight node_weight = G.getNodeWeight(node); + bool own_block_balanced = G.getBlockSize(old_block) <= cluster_upperbound || !balance; + + if( G.getNodeDegree(node) == 0) { + // find a block to assign it to + if(config.vcycle) { + NodeWeight prev_block_size = G.getBlockSize( G.getNodeLabel( prev_node ) ); + bool same_block = G.getSecondPartitionIndex(prev_node)==G.getSecondPartitionIndex(node); + if( prev_block_size + node_weight <= cluster_upperbound && same_block ) { + max_block = G.getNodeLabel( prev_node ); + } } else { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - PartitionID cur_block = G.getNodeLabel(target); - hash_map[cur_block] += G.getEdgeWeight(e); - PartitionID cur_value = hash_map[cur_block]; - - bool improvement = cur_value > max_value; - improvement |= cur_value == max_value && random_functions::nextBool(); - - bool sizeconstraint = G.getBlockSize(cur_block) + node_weight <= cluster_upperbound; - sizeconstraint |= cur_block == old_block; - - bool cycle = !config.vcycle; - cycle |= G.getSecondPartitionIndex( node ) == G.getSecondPartitionIndex(target); - - bool balancing = own_block_balanced || cur_block != old_block; - if( improvement && sizeconstraint && cycle && balancing) { - max_value = cur_value; - max_block = cur_block; - } - } endfor + NodeWeight prev_block_size = G.getBlockSize( G.getNodeLabel( prev_node ) ); + if( prev_block_size + node_weight <= cluster_upperbound) { + max_block = G.getNodeLabel( prev_node ); + } } - if( old_block != max_block ) { - G.setNodeLabel(node, max_block); + } else { + forall_out_edges(G, e, node) { + NodeID target = G.getEdgeTarget(e); + PartitionID cur_block = G.getNodeLabel(target); + hash_map[cur_block] += G.getEdgeWeight(e); + PartitionID cur_value = hash_map[cur_block]; - G.setBlockSize(old_block, G.getBlockSize(old_block) - node_weight); - G.setBlockSize(max_block, G.getBlockSize(max_block) + node_weight); - } + bool improvement = cur_value > max_value; + improvement |= cur_value == max_value && random_functions::nextBool(); + + bool sizeconstraint = G.getBlockSize(cur_block) + node_weight <= cluster_upperbound; + sizeconstraint |= cur_block == old_block; - prev_node = node; - G.update_ghost_node_data(); - hash_map.clear(); + bool cycle = !config.vcycle; + cycle |= G.getSecondPartitionIndex( node ) == G.getSecondPartitionIndex(target); - } endfor - G.update_ghost_node_data_finish(); - } + bool balancing = own_block_balanced || cur_block != old_block; + if( improvement && sizeconstraint && cycle && balancing) { + max_value = cur_value; + max_block = cur_block; + } + } endfor + } + + if( old_block != max_block ) { + G.setNodeLabel(node, max_block); + + G.setBlockSize(old_block, G.getBlockSize(old_block) - node_weight); + G.setBlockSize(max_block, G.getBlockSize(max_block) + node_weight); + } + + prev_node = node; + G.update_ghost_node_data(); + hash_map.clear(); + + } endfor + G.update_ghost_node_data_finish(); } + } }; - +} #endif /* end of include guard: PARALLEL_LABEL_COMPRESS_9ME4H8DK */ diff --git a/parallel/parallel_src/lib/partition_config.h b/parallel/parallel_src/lib/partition_config.h index 0a4ef1f3..93e3142a 100644 --- a/parallel/parallel_src/lib/partition_config.h +++ b/parallel/parallel_src/lib/partition_config.h @@ -9,121 +9,119 @@ #define PARTITION_CONFIG_DI1ES4T0A #include "definitions.h" - +namespace parhip { // Configuration for the partitioning. struct PPartitionConfig { - PPartitionConfig() {} + //======================================= + //============ Graph Gen================= + //======================================= + int log_num_verts{}; - //======================================= - //============ Graph Gen================= - //======================================= - int log_num_verts; + long edge_factor{}; - long edge_factor; + bool generate_rgg{}; - bool generate_rgg; + bool generate_ba{}; - bool generate_ba; + //======================================= + //============ Communication ============ + //======================================= - //======================================= - //============ Communication ============ - //======================================= + ULONG comm_rounds{}; - ULONG comm_rounds; + //======================================= + //============ Global Data=============== + //======================================= - //======================================= - //============ Global Data=============== - //======================================= + NodeID number_of_overall_nodes{}; - NodeID number_of_overall_nodes; + //======================================= + //===============MISC==================== + //======================================= - //======================================= - //===============MISC==================== - //======================================= + PermutationQuality permutation_quality{}; - PermutationQuality permutation_quality; + unsigned int label_iterations{}; - unsigned int label_iterations; - - unsigned int label_iterations_coarsening; + unsigned int label_iterations_coarsening{}; - unsigned int label_iterations_refinement; + unsigned int label_iterations_refinement{}; - double cluster_coarsening_factor; + double cluster_coarsening_factor{}; - double time_limit; + double time_limit{}; - unsigned epsilon; + unsigned epsilon{}; - unsigned inbalance; + unsigned inbalance{}; - std::string input_partition; + std::string input_partition{}; - int seed; + int seed{}; - PartitionID k; + PartitionID k{}; - std::string graph_filename; + std::string graph_filename{}; - std::string input_partition_filename; + std::string input_partition_filename{}; - int evolutionary_time_limit; + int evolutionary_time_limit{}; - NodeWeight upper_bound_partition; + NodeWeight upper_bound_partition{}; - NodeWeight upper_bound_cluster; + NodeWeight upper_bound_cluster{}; - NodeID total_num_labels; + NodeID total_num_labels{}; - InitialPartitioningAlgorithm initial_partitioning_algorithm; + InitialPartitioningAlgorithm initial_partitioning_algorithm{}; - int stop_factor; + int stop_factor{}; - bool vcycle; + bool vcycle{}; - int num_vcycles; + int num_vcycles{}; - int num_tries; // number of repetitions to perform + int num_tries{}; // number of repetitions to perform - NodeOrderingType node_ordering; + NodeOrderingType node_ordering{}; - bool no_refinement_in_last_iteration; + bool no_refinement_in_last_iteration{}; - double ht_fill_factor; + double ht_fill_factor{}; - bool eco; + bool eco{}; - int binary_io_window_size; + int binary_io_window_size{}; - ULONG barabasi_albert_mindegree; + ULONG barabasi_albert_mindegree{}; - bool compute_degree_sequence_ba; + bool compute_degree_sequence_ba{}; - bool compute_degree_sequence_k_first; + bool compute_degree_sequence_k_first{}; - bool kronecker_internal_only; + bool kronecker_internal_only{}; - ULONG k_deg; + ULONG k_deg{}; - bool generate_ba_32bit; + bool generate_ba_32bit{}; - ULONG n; + ULONG n{}; - bool save_partition; + bool save_partition{}; - bool save_partition_binary; + bool save_partition_binary{}; - bool vertex_degree_weights; + bool vertex_degree_weights{}; - bool converter_evaluate; + bool converter_evaluate{}; - //======================================= - //===============Shared Mem OMP========== - //======================================= - void LogDump(FILE *out) const { - } + //======================================= + //===============Shared Mem OMP========== + //======================================= + void LogDump(FILE *out) const { + } }; - +} #endif /* end of include guard: PARTITION_CONFIG_DI1ES4T0 */ diff --git a/parallel/parallel_src/lib/tools/distributed_quality_metrics.cpp b/parallel/parallel_src/lib/tools/distributed_quality_metrics.cpp index 80f72938..7a5d5b18 100644 --- a/parallel/parallel_src/lib/tools/distributed_quality_metrics.cpp +++ b/parallel/parallel_src/lib/tools/distributed_quality_metrics.cpp @@ -6,299 +6,712 @@ *****************************************************************************/ #include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_fixed_reduction.h" +#include "definitions.h" #include "distributed_quality_metrics.h" - -distributed_quality_metrics::distributed_quality_metrics() { - +namespace parhip { +namespace { +[[nodiscard]] auto validated_local_block_count( + PartitionID k, + mpi::communicator_view communicator, + std::string_view zero_diagnostic, + std::string_view capacity_diagnostic) noexcept -> std::size_t { + mpi::require_live_intracommunicator( + communicator, "local quality metric requires a live intracommunicator"); + if (k == 0) { + mpi::abort_on_programming_error(communicator.native_handle(), + zero_diagnostic); + } + if (!std::in_range(k)) { + mpi::abort_on_capacity_failure(communicator.native_handle(), + "local quality metric", capacity_diagnostic); + } + return static_cast(k); } -distributed_quality_metrics::~distributed_quality_metrics() { - +[[nodiscard]] auto validated_block_count( + PartitionID k, + mpi::communicator_view communicator, + std::string_view zero_diagnostic, + std::string_view mismatch_diagnostic, + std::string_view capacity_diagnostic) noexcept -> std::size_t { + mpi::require_live_intracommunicator( + communicator, + "distributed quality metric requires a live intracommunicator"); + static_assert(sizeof(PartitionID) <= sizeof(std::uint64_t)); + auto const local = static_cast(k); + auto minimum = std::uint64_t{}; + auto maximum = std::uint64_t{}; + mpi::check_or_abort(MPI_Allreduce(&local, &minimum, 1, MPI_UINT64_T, MPI_MIN, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(distributed block count minimum)"); + mpi::check_or_abort(MPI_Allreduce(&local, &maximum, 1, MPI_UINT64_T, MPI_MAX, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(distributed block count maximum)"); + if (minimum != maximum) { + mpi::abort_on_programming_error(communicator.native_handle(), + mismatch_diagnostic); + } + if (k == 0) { + mpi::abort_on_programming_error(communicator.native_handle(), + zero_diagnostic); + } + if (!std::in_range(k)) { + mpi::abort_on_capacity_failure(communicator.native_handle(), + "distributed quality metric", + capacity_diagnostic); + } + return static_cast(k); } -EdgeWeight distributed_quality_metrics::edge_cut_second( parallel_graph_access & G, MPI_Comm communicator ) { - EdgeWeight local_cut = 0; - forall_local_nodes(G, node) { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if( G.getSecondPartitionIndex( node ) != G.getSecondPartitionIndex(target)) { - local_cut += G.getEdgeWeight(e); - } - } endfor - } endfor - - EdgeWeight global_cut = 0; - MPI_Allreduce(&local_cut, &global_cut, 1, MPI_UNSIGNED_LONG_LONG, MPI_SUM, communicator); - - return global_cut/2; +void require_collective_condition(bool local_condition, + mpi::communicator_view communicator, + std::string_view diagnostic) noexcept { + auto const local = local_condition ? 1 : 0; + auto all_are_valid = 0; + mpi::check_or_abort(MPI_Allreduce(&local, &all_are_valid, 1, MPI_INT, MPI_MIN, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(distributed quality-metric validation)"); + if (all_are_valid == 0) { + mpi::abort_on_programming_error(communicator.native_handle(), diagnostic); + } } -EdgeWeight distributed_quality_metrics::local_edge_cut( parallel_graph_access & G, int* partition_map, MPI_Comm communicator ) { - EdgeWeight local_cut = 0; - forall_local_nodes(G, node) { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if( partition_map[ node ] != partition_map[ target ]) { - local_cut += G.getEdgeWeight(e); - } - } endfor - } endfor - - return local_cut/2; +template +[[nodiscard]] constexpr auto checked_add(T& accumulator, T value) noexcept + -> bool { + if (value > std::numeric_limits::max() - accumulator) { + return false; + } + accumulator += value; + return true; } -EdgeWeight distributed_quality_metrics::edge_cut( parallel_graph_access & G, MPI_Comm communicator ) { - EdgeWeight local_cut = 0; - forall_local_nodes(G, node) { - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if( G.getNodeLabel( node ) != G.getNodeLabel(target)) { - local_cut += G.getEdgeWeight(e); - } - } endfor - } endfor - - EdgeWeight global_cut = 0; - MPI_Allreduce(&local_cut, &global_cut, 1, MPI_UNSIGNED_LONG_LONG, MPI_SUM, communicator); - - return global_cut/2; +void require_collective_capacity(bool local_condition, + mpi::communicator_view communicator, + std::string_view boundary, + std::string_view diagnostic) noexcept { + auto const local = local_condition ? 1 : 0; + auto all_are_representable = 0; + mpi::check_or_abort( + MPI_Allreduce(&local, &all_are_representable, 1, MPI_INT, MPI_MIN, + communicator.native_handle()), + communicator.native_handle(), + "MPI_Allreduce(distributed quality-metric capacity validation)"); + if (all_are_representable == 0) { + mpi::abort_on_capacity_failure(communicator.native_handle(), boundary, + diagnostic); + } } -NodeWeight distributed_quality_metrics::local_max_block_weight( PPartitionConfig & config, parallel_graph_access & G, int * partition_map, MPI_Comm communicator ) { - std::vector block_weights(config.k, 0); - - NodeWeight graph_vertex_weight = 0; - - forall_local_nodes(G, n) { - PartitionID curPartition = partition_map[n]; - block_weights[curPartition] += G.getNodeWeight(n); - graph_vertex_weight += G.getNodeWeight(n); - } endfor - - NodeWeight cur_max = 0; - - for( PartitionID block = 0; block < config.k; block++) { - NodeWeight cur_weight = block_weights[block]; - if (cur_weight > cur_max) { - cur_max = cur_weight; - } - } - - return cur_max; +[[nodiscard]] auto exact_balance_ratio( + std::span block_weights, + NodeWeight divisor, + mpi::communicator_view communicator, + std::string_view boundary, + std::string_view total_overflow_diagnostic) noexcept -> double { + if (block_weights.empty() || divisor == 0) { + mpi::abort_on_programming_error( + communicator.native_handle(), + "balance ratio requires nonempty blocks and a positive divisor"); + } + auto total_weight = NodeWeight{}; + for (auto const weight : block_weights) { + if (!checked_add(total_weight, weight)) { + mpi::abort_on_capacity_failure(communicator.native_handle(), boundary, + total_overflow_diagnostic); + } + } + // Empty graphs and graphs whose modeled load is identically zero are + // neutrally balanced by convention. + if (total_weight == 0) { + return 1.0; + } + auto const maximum_weight = std::ranges::max(block_weights); + auto const ideal_weight = + total_weight / divisor + (total_weight % divisor != 0 ? 1 : 0); + return static_cast(maximum_weight) / + static_cast(ideal_weight); } - -double distributed_quality_metrics::balance( PPartitionConfig & config, parallel_graph_access & G, MPI_Comm communicator ) { - std::vector block_weights(config.k, 0); - - NodeWeight local_graph_vertex_weight = 0; - - forall_local_nodes(G, n) { - PartitionID curPartition = G.getNodeLabel(n); - block_weights[curPartition] += G.getNodeWeight(n); - local_graph_vertex_weight += G.getNodeWeight(n); - } endfor - - std::vector overall_weights(config.k, 0); - MPI_Allreduce(&block_weights[0], &overall_weights[0], config.k, MPI_UNSIGNED_LONG_LONG, MPI_SUM, communicator); - - NodeWeight graph_vertex_weight = 0; - MPI_Allreduce(&local_graph_vertex_weight, &graph_vertex_weight, 1, MPI_UNSIGNED_LONG_LONG, MPI_SUM, communicator); - - double balance_part_weight = ceil(graph_vertex_weight / (double)config.k); - double cur_max = -1; - - for( PartitionID block = 0; block < config.k; block++) { - double cur = overall_weights[block]; - if (cur > cur_max) { - cur_max = cur; - } +} // namespace + +EdgeWeight distributed_quality_metrics::edge_cut_second( + parallel_graph_access& G, + MPI_Comm communicator) { + auto const communicator_view = mpi::communicator_view{communicator}; + try { + auto local_cut = EdgeWeight{}; + auto local_cut_is_representable = true; + for (NodeID node = 0, node_end = G.number_of_local_nodes(); node < node_end; + ++node) { + for (EdgeID edge = G.get_first_edge(node), + edge_end = G.get_first_invalid_edge(node); + edge < edge_end; ++edge) { + auto const target = G.getEdgeTarget(edge); + if (G.getSecondPartitionIndex(node) != + G.getSecondPartitionIndex(target)) { + local_cut_is_representable = + checked_add(local_cut, G.getEdgeWeight(edge)) && + local_cut_is_representable; } - - double percentage = cur_max/balance_part_weight; - return percentage; + } + } + require_collective_capacity( + local_cut_is_representable, communicator_view, + "distributed second edge cut", + "local edge-cut sum exceeds EdgeWeight capacity"); + + auto const local = std::array{local_cut}; + auto global = std::array{}; + mpi::all_reduce_checked_sum( + std::span{local}, std::span{global}, + communicator_view, "MPI_Allreduce(distributed second edge cut)", + "distributed second edge cut", + "global edge-cut sum exceeds EdgeWeight capacity"); + return global[0] / 2; + } catch (...) { + mpi::abort_on_exception(communicator_view.native_handle(), + "distributed second edge-cut computation failed"); + } } -double distributed_quality_metrics::balance_second( PPartitionConfig & config, parallel_graph_access & G, MPI_Comm communicator ) { - std::vector block_weights(config.k, 0); - - NodeWeight local_graph_vertex_weight = 0; - - forall_local_nodes(G, n) { - PartitionID curPartition = G.getSecondPartitionIndex(n); - block_weights[curPartition] += G.getNodeWeight(n); - local_graph_vertex_weight += G.getNodeWeight(n); - } endfor - - std::vector overall_weights(config.k, 0); - MPI_Allreduce(&block_weights[0], &overall_weights[0], config.k, MPI_UNSIGNED_LONG_LONG, MPI_SUM, communicator); - - NodeWeight graph_vertex_weight = 0; - MPI_Allreduce(&local_graph_vertex_weight, &graph_vertex_weight, 1, MPI_UNSIGNED_LONG_LONG, MPI_SUM, communicator); - - double balance_part_weight = ceil(graph_vertex_weight / (double)config.k); - double cur_max = -1; - - for( PartitionID block = 0; block < config.k; block++) { - double cur = overall_weights[block]; - if (cur > cur_max) { - cur_max = cur; - } +EdgeWeight distributed_quality_metrics::local_edge_cut(parallel_graph_access& G, + int* partition_map, + MPI_Comm communicator) { + auto const communicator_view = mpi::communicator_view{communicator}; + mpi::require_live_intracommunicator( + communicator_view, "local edge cut requires a live intracommunicator"); + if (G.number_of_local_nodes() != 0 && partition_map == nullptr) { + mpi::abort_on_programming_error( + communicator_view.native_handle(), + "local edge cut requires a partition map for nonempty local storage"); + } + try { + auto local_cut = EdgeWeight{}; + for (NodeID node = 0, node_end = G.number_of_local_nodes(); node < node_end; + ++node) { + for (EdgeID edge = G.get_first_edge(node), + edge_end = G.get_first_invalid_edge(node); + edge < edge_end; ++edge) { + auto const target = G.getEdgeTarget(edge); + if (partition_map[node] != partition_map[target] && + !checked_add(local_cut, G.getEdgeWeight(edge))) { + mpi::abort_on_capacity_failure( + communicator_view.native_handle(), "local edge cut", + "local edge-cut sum exceeds EdgeWeight capacity"); } - - double percentage = cur_max/balance_part_weight; - return percentage; + } + } + return local_cut / 2; + } catch (...) { + mpi::abort_on_exception(communicator_view.native_handle(), + "local edge-cut computation failed"); + } } - -double distributed_quality_metrics::balance_load( PPartitionConfig & config, parallel_graph_access & G, MPI_Comm communicator ) { - std::vector block_weights(config.k, 0); - - NodeWeight local_weight = 0; - forall_local_nodes(G, n) { - PartitionID curPartition = G.getNodeLabel(n); - block_weights[curPartition] += G.getNodeWeight(n)+G.getNodeDegree(n); - local_weight += G.getNodeWeight(n)+G.getNodeDegree(n); - } endfor - - std::vector overall_weights(config.k, 0); - MPI_Allreduce(&block_weights[0], &overall_weights[0], config.k, MPI_UNSIGNED_LONG_LONG, MPI_SUM, communicator); - - NodeWeight total_weight = 0; - MPI_Allreduce(&local_weight, &total_weight, 1, MPI_UNSIGNED_LONG_LONG, MPI_SUM, communicator); - - double balance_part_weight = ceil(total_weight / (double)config.k); - double cur_max = -1; - - for( PartitionID block = 0; block < config.k; block++) { - double cur = overall_weights[block]; - if (cur > cur_max) { - cur_max = cur; - } +EdgeWeight distributed_quality_metrics::edge_cut(parallel_graph_access& G, + MPI_Comm communicator) { + auto const communicator_view = mpi::communicator_view{communicator}; + try { + auto local_cut = EdgeWeight{}; + auto local_cut_is_representable = true; + for (NodeID node = 0, node_end = G.number_of_local_nodes(); node < node_end; + ++node) { + for (EdgeID edge = G.get_first_edge(node), + edge_end = G.get_first_invalid_edge(node); + edge < edge_end; ++edge) { + auto const target = G.getEdgeTarget(edge); + if (G.getNodeLabel(node) != G.getNodeLabel(target)) { + local_cut_is_representable = + checked_add(local_cut, G.getEdgeWeight(edge)) && + local_cut_is_representable; } + } + } + require_collective_capacity( + local_cut_is_representable, communicator_view, "distributed edge cut", + "local edge-cut sum exceeds EdgeWeight capacity"); + + auto const local = std::array{local_cut}; + auto global = std::array{}; + mpi::all_reduce_checked_sum( + std::span{local}, std::span{global}, + communicator_view, "MPI_Allreduce(distributed edge cut)", + "distributed edge cut", + "global edge-cut sum exceeds EdgeWeight capacity"); + return global[0] / 2; + } catch (...) { + mpi::abort_on_exception(communicator_view.native_handle(), + "distributed edge-cut computation failed"); + } +} - double percentage = cur_max/balance_part_weight; - return percentage; +NodeWeight distributed_quality_metrics::local_max_block_weight( + PPartitionConfig& config, + parallel_graph_access& G, + int* partition_map, + MPI_Comm communicator) { + auto const communicator_view = mpi::communicator_view{communicator}; + auto const block_count = validated_local_block_count( + config.k, communicator_view, + "local maximum block weight requires k greater than zero", + "local maximum block count exceeds addressable storage"); + if (G.number_of_local_nodes() != 0 && partition_map == nullptr) { + mpi::abort_on_programming_error( + communicator_view.native_handle(), + "local maximum block weight requires a partition map for nonempty " + "local storage"); + } + try { + auto block_weights = std::vector(block_count, 0); + auto labels_are_valid = true; + auto sums_are_representable = true; + + for (NodeID node = 0, node_end = G.number_of_local_nodes(); node < node_end; + ++node) { + auto const block = static_cast(partition_map[node]); + if (block >= config.k) { + labels_are_valid = false; + continue; + } + sums_are_representable = + checked_add(block_weights[static_cast(block)], + G.getNodeWeight(node)) && + sums_are_representable; + } + if (!labels_are_valid) { + mpi::abort_on_programming_error( + communicator_view.native_handle(), + "local maximum block weight label is outside [0, k)"); + } + if (!sums_are_representable) { + mpi::abort_on_capacity_failure( + communicator_view.native_handle(), "local maximum block weight", + "local block-weight sum exceeds NodeWeight capacity"); + } + return std::ranges::max(block_weights); + } catch (...) { + mpi::abort_on_exception(communicator_view.native_handle(), + "local maximum block-weight computation failed"); + } +} +double distributed_quality_metrics::balance(PPartitionConfig& config, + parallel_graph_access& G, + MPI_Comm communicator) { + auto const communicator_view = mpi::communicator_view{communicator}; + auto const block_count = validated_block_count( + config.k, communicator_view, + "distributed balance requires k greater than zero", + "distributed balance k differs across communicator", + "distributed balance block count exceeds addressable storage"); + try { + auto block_weights = std::vector(block_count, 0); + auto local_graph_vertex_weight = NodeWeight{}; + auto labels_are_valid = true; + auto sums_are_representable = true; + + for (NodeID node = 0, node_end = G.number_of_local_nodes(); node < node_end; + ++node) { + auto const block = static_cast(G.getNodeLabel(node)); + auto const weight = G.getNodeWeight(node); + sums_are_representable = checked_add(local_graph_vertex_weight, weight) && + sums_are_representable; + if (block >= config.k) { + labels_are_valid = false; + continue; + } + sums_are_representable = + checked_add(block_weights[static_cast(block)], weight) && + sums_are_representable; + } + require_collective_capacity( + sums_are_representable, communicator_view, "distributed balance", + "local vertex-weight sum exceeds NodeWeight capacity"); + require_collective_condition(labels_are_valid, communicator_view, + "distributed balance label is outside [0, k)"); + + auto overall_weights = std::vector(block_count, 0); + mpi::all_reduce_checked_sum( + std::span{block_weights}, std::span{overall_weights}, + communicator_view, "MPI_Allreduce(distributed block weights)", + "distributed balance", + "global block-weight sum exceeds NodeWeight capacity"); + return exact_balance_ratio( + overall_weights, config.k, communicator_view, "distributed balance", + "global vertex-weight sum exceeds NodeWeight capacity"); + } catch (...) { + mpi::abort_on_exception(communicator_view.native_handle(), + "distributed balance computation failed"); + } } -double distributed_quality_metrics::balance_load_dist( PPartitionConfig & config, parallel_graph_access & G, MPI_Comm communicator ) { - - int rank, size; - MPI_Comm_rank( communicator, &rank); - MPI_Comm_size( communicator, &size); - - std::vector block_weights(size, 0); - - NodeWeight local_weight = 0; - forall_local_nodes(G, n) { - block_weights[rank] += G.getNodeWeight(n)+G.getNodeDegree(n); - local_weight += G.getNodeWeight(n)+G.getNodeDegree(n); - } endfor - - std::vector overall_weights(size, 0); - MPI_Allreduce(&block_weights[0], &overall_weights[0], size, MPI_UNSIGNED_LONG_LONG, MPI_SUM, communicator); - - NodeWeight total_weight = 0; - MPI_Allreduce(&local_weight, &total_weight, 1, MPI_UNSIGNED_LONG_LONG, MPI_SUM, communicator); - - double balance_part_weight = ceil(total_weight / (double)size); - double cur_max = -1; - - for( PartitionID block = 0; block < (PartitionID)size; block++) { - double cur = overall_weights[block]; - if (cur > cur_max) { - cur_max = cur; - } - } +double distributed_quality_metrics::balance_second(PPartitionConfig& config, + parallel_graph_access& G, + MPI_Comm communicator) { + auto const communicator_view = mpi::communicator_view{communicator}; + auto const block_count = validated_block_count( + config.k, communicator_view, + "distributed second balance requires k greater than zero", + "distributed second balance k differs across communicator", + "distributed second balance block count exceeds addressable storage"); + try { + auto block_weights = std::vector(block_count, 0); + auto local_graph_vertex_weight = NodeWeight{}; + auto labels_are_valid = true; + auto sums_are_representable = true; + + for (NodeID node = 0, node_end = G.number_of_local_nodes(); node < node_end; + ++node) { + auto const block = + static_cast(G.getSecondPartitionIndex(node)); + auto const weight = G.getNodeWeight(node); + sums_are_representable = checked_add(local_graph_vertex_weight, weight) && + sums_are_representable; + if (block >= config.k) { + labels_are_valid = false; + continue; + } + sums_are_representable = + checked_add(block_weights[static_cast(block)], weight) && + sums_are_representable; + } + require_collective_capacity( + sums_are_representable, communicator_view, "distributed second balance", + "local vertex-weight sum exceeds NodeWeight capacity"); + require_collective_condition( + labels_are_valid, communicator_view, + "distributed second balance label is outside [0, k)"); + + auto overall_weights = std::vector(block_count, 0); + mpi::all_reduce_checked_sum( + std::span{block_weights}, std::span{overall_weights}, + communicator_view, "MPI_Allreduce(distributed second block weights)", + "distributed second balance", + "global block-weight sum exceeds NodeWeight capacity"); + return exact_balance_ratio( + overall_weights, config.k, communicator_view, + "distributed second balance", + "global vertex-weight sum exceeds NodeWeight capacity"); + } catch (...) { + mpi::abort_on_exception(communicator_view.native_handle(), + "distributed second-balance computation failed"); + } +} - double percentage = cur_max/balance_part_weight; - return percentage; +double distributed_quality_metrics::balance_load(PPartitionConfig& config, + parallel_graph_access& G, + MPI_Comm communicator) { + auto const communicator_view = mpi::communicator_view{communicator}; + auto const block_count = validated_block_count( + config.k, communicator_view, + "distributed load balance requires k greater than zero", + "distributed load balance k differs across communicator", + "distributed load balance block count exceeds addressable storage"); + try { + auto block_weights = std::vector(block_count, 0); + auto local_weight = NodeWeight{}; + auto labels_are_valid = true; + auto sums_are_representable = true; + for (NodeID node = 0, node_end = G.number_of_local_nodes(); node < node_end; + ++node) { + auto const block = static_cast(G.getNodeLabel(node)); + auto const node_weight = G.getNodeWeight(node); + auto const degree = G.getNodeDegree(node); + auto node_load = node_weight; + auto const node_load_is_representable = + std::in_range(degree) && + checked_add(node_load, static_cast(degree)); + sums_are_representable = + node_load_is_representable && sums_are_representable; + if (block >= config.k) { + labels_are_valid = false; + continue; + } + if (!node_load_is_representable) { + continue; + } + sums_are_representable = + checked_add(block_weights[static_cast(block)], + node_load) && + checked_add(local_weight, node_load) && sums_are_representable; + } + require_collective_capacity( + sums_are_representable, communicator_view, "distributed load balance", + "local node-load sum exceeds NodeWeight capacity"); + require_collective_condition( + labels_are_valid, communicator_view, + "distributed load balance label is outside [0, k)"); + + auto overall_weights = std::vector(block_count, 0); + mpi::all_reduce_checked_sum( + std::span{block_weights}, std::span{overall_weights}, + communicator_view, "MPI_Allreduce(distributed load block weights)", + "distributed load balance", + "global block-load sum exceeds NodeWeight capacity"); + return exact_balance_ratio(overall_weights, config.k, communicator_view, + "distributed load balance", + "global total load exceeds NodeWeight capacity"); + } catch (...) { + mpi::abort_on_exception(communicator_view.native_handle(), + "distributed load-balance computation failed"); + } +} +double distributed_quality_metrics::balance_load_dist(PPartitionConfig&, + parallel_graph_access& G, + MPI_Comm communicator) { + auto const communicator_view = mpi::communicator_view{communicator}; + auto const rank = communicator_view.rank(); + auto const size = communicator_view.size(); + try { + auto rank_weights = + std::vector(static_cast(size), 0); + auto local_weight = NodeWeight{}; + auto sums_are_representable = true; + for (NodeID node = 0, node_end = G.number_of_local_nodes(); node < node_end; + ++node) { + auto node_load = G.getNodeWeight(node); + auto const degree = G.getNodeDegree(node); + auto const node_load_is_representable = + std::in_range(degree) && + checked_add(node_load, static_cast(degree)); + sums_are_representable = + node_load_is_representable && sums_are_representable; + if (node_load_is_representable) { + sums_are_representable = + checked_add(local_weight, node_load) && sums_are_representable; + } + } + rank_weights[static_cast(rank)] = local_weight; + require_collective_capacity( + sums_are_representable, communicator_view, + "distributed rank-load balance", + "local node-load sum exceeds NodeWeight capacity"); + + auto overall_weights = + std::vector(static_cast(size), 0); + mpi::all_reduce_checked_sum( + std::span{rank_weights}, std::span{overall_weights}, + communicator_view, "MPI_Allreduce(distributed rank load weights)", + "distributed rank-load balance", + "global rank-load sum exceeds NodeWeight capacity"); + return exact_balance_ratio(overall_weights, static_cast(size), + communicator_view, + "distributed rank-load balance", + "global total load exceeds NodeWeight capacity"); + } catch (...) { + mpi::abort_on_exception(communicator_view.native_handle(), + "distributed rank-load balance computation failed"); + } } // measure the communication volume of the current graph distribution -EdgeWeight distributed_quality_metrics::comm_vol( PPartitionConfig & config, parallel_graph_access & G, MPI_Comm communicator ) { - EdgeWeight local_comm_vol = 0; int rank; - MPI_Comm_rank( communicator, &rank); - - std::vector block_volume(config.k, 0); - forall_local_nodes(G, node) { - std::vector block_incident(config.k, false); - PartitionID block = G.getNodeLabel( node ); - block_incident[block] = true; - int num_incident_blocks = 0; - - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - PartitionID target_block = G.getNodeLabel( target ); - if(!block_incident[target_block]) { - block_incident[target_block] = true; - num_incident_blocks++; - } - } endfor - block_volume[block] += num_incident_blocks; - } endfor - - std::vector overall_weights(config.k, 0); - MPI_Allreduce(&block_volume[0], &overall_weights[0], config.k, MPI_UNSIGNED_LONG_LONG, MPI_SUM, communicator); - - if( rank == ROOT ) { - EdgeWeight total_comm_vol = 0; - for( PEID i = 0; i < (PEID)overall_weights.size(); i++) { - total_comm_vol += overall_weights[i]; - } - EdgeWeight max_comm_vol = *(std::max_element(overall_weights.begin(), overall_weights.end())); - EdgeWeight min_comm_vol = *(std::min_element(overall_weights.begin(), overall_weights.end())); - - std::cout << "log> total vol part " << total_comm_vol << std::endl; - std::cout << "log> max vol part " << max_comm_vol << std::endl; - std::cout << "log> min vol part " << min_comm_vol << std::endl; - std::cout << "log> vol part ratio " << max_comm_vol/(double)min_comm_vol << std::endl; +EdgeWeight distributed_quality_metrics::comm_vol(PPartitionConfig& config, + parallel_graph_access& G, + MPI_Comm communicator) { + auto const communicator_view = mpi::communicator_view{communicator}; + auto const rank = communicator_view.rank(); + + auto const block_count = validated_block_count( + config.k, communicator_view, + "distributed communication volume requires k greater than zero", + "distributed communication volume k differs across communicator", + "distributed communication-volume block count exceeds addressable " + "storage"); + try { + auto block_volume = std::vector(block_count, 0); + auto labels_are_valid = true; + auto local_volume_is_representable = true; + for (NodeID node = 0, node_end = G.number_of_local_nodes(); node < node_end; + ++node) { + std::vector block_incident(block_count, false); + auto const block = static_cast(G.getNodeLabel(node)); + auto const block_is_valid = block < config.k; + labels_are_valid = labels_are_valid && block_is_valid; + if (block_is_valid) { + block_incident[block] = true; + } + auto num_incident_blocks = EdgeWeight{}; + + for (EdgeID edge = G.get_first_edge(node), + edge_end = G.get_first_invalid_edge(node); + edge < edge_end; ++edge) { + auto const target = G.getEdgeTarget(edge); + auto const target_block = + static_cast(G.getNodeLabel(target)); + if (target_block >= config.k) { + labels_are_valid = false; + continue; } - - return local_comm_vol; + if (!block_incident[target_block]) { + block_incident[target_block] = true; + local_volume_is_representable = + checked_add(num_incident_blocks, EdgeWeight{1}) && + local_volume_is_representable; + } + } + if (block_is_valid) { + local_volume_is_representable = + checked_add(block_volume[static_cast(block)], + num_incident_blocks) && + local_volume_is_representable; + } + } + require_collective_capacity( + local_volume_is_representable, communicator_view, + "distributed communication volume", + "local communication-volume sum exceeds EdgeWeight capacity"); + require_collective_condition( + labels_are_valid, communicator_view, + "distributed communication-volume label is outside [0, k)"); + + auto overall_weights = std::vector(block_count, 0); + mpi::all_reduce_checked_sum( + std::span{block_volume}, std::span{overall_weights}, + communicator_view, + "MPI_Allreduce(distributed communication volume by block)", + "distributed communication volume", + "global block communication-volume sum exceeds EdgeWeight capacity"); + + auto total_comm_vol = EdgeWeight{}; + for (auto const weight : overall_weights) { + if (!checked_add(total_comm_vol, weight)) { + mpi::abort_on_capacity_failure( + communicator_view.native_handle(), + "distributed communication volume", + "total communication volume exceeds EdgeWeight capacity"); + } + } + if (rank == ROOT) { + EdgeWeight max_comm_vol = + *(std::max_element(overall_weights.begin(), overall_weights.end())); + EdgeWeight min_comm_vol = + *(std::min_element(overall_weights.begin(), overall_weights.end())); + + std::cout << "log> total vol part " << total_comm_vol << std::endl; + std::cout << "log> max vol part " << max_comm_vol << std::endl; + std::cout << "log> min vol part " << min_comm_vol << std::endl; + if (min_comm_vol == 0) { + std::cout << "log> vol part ratio undefined" << std::endl; + } else { + std::cout << "log> vol part ratio " + << max_comm_vol / static_cast(min_comm_vol) + << std::endl; + } + } + + return total_comm_vol; + } catch (...) { + mpi::abort_on_exception( + communicator_view.native_handle(), + "distributed communication-volume computation failed"); + } } // measure the communication volume of the current graph distribution -EdgeWeight distributed_quality_metrics::comm_vol_dist( parallel_graph_access & G, MPI_Comm communicator ) { - EdgeWeight local_comm_vol = 0; - int rank, size; - MPI_Comm_rank( communicator, &rank); - MPI_Comm_size( communicator, &size); - - forall_local_nodes(G, node) { - std::vector block_incident(size, false); - block_incident[rank] = true; - int num_incident_blocks = 0; - - forall_out_edges(G, e, node) { - NodeID target = G.getEdgeTarget(e); - if( !G.is_local_node( target ) ) { - PartitionID target_block = G.getTargetPE( target ); - if(!block_incident[target_block]) { - block_incident[target_block] = true; - num_incident_blocks++; - } - } - } endfor - local_comm_vol += num_incident_blocks; - } endfor - - EdgeWeight total_comm_vol = 0; - EdgeWeight max_comm_vol = 0; - EdgeWeight min_comm_vol = 0; - - MPI_Reduce(&local_comm_vol, &total_comm_vol, 1, MPI_UNSIGNED_LONG_LONG, MPI_SUM, ROOT, communicator); - MPI_Reduce(&local_comm_vol, &max_comm_vol, 1, MPI_UNSIGNED_LONG_LONG, MPI_MAX, ROOT, communicator); - MPI_Reduce(&local_comm_vol, &min_comm_vol, 1, MPI_UNSIGNED_LONG_LONG, MPI_MIN, ROOT, communicator); - - if( rank == ROOT ) { - std::cout << "log> total vol currentdist " << total_comm_vol << std::endl; - std::cout << "log> max vol currentdist " << max_comm_vol << std::endl; - std::cout << "log> min vol currentdist " << min_comm_vol << std::endl; - std::cout << "log> vol dist currentratio " << max_comm_vol/(double)min_comm_vol << std::endl; +EdgeWeight distributed_quality_metrics::comm_vol_dist(parallel_graph_access& G, + MPI_Comm communicator) { + auto local_comm_vol = EdgeWeight{}; + auto const communicator_view = mpi::communicator_view{communicator}; + auto const rank = communicator_view.rank(); + auto const size = communicator_view.size(); + try { + auto target_owners_are_valid = true; + auto local_volume_is_representable = true; + + for (NodeID node = 0, node_end = G.number_of_local_nodes(); node < node_end; + ++node) { + std::vector block_incident(static_cast(size), false); + block_incident[static_cast(rank)] = true; + auto num_incident_blocks = EdgeWeight{}; + + for (EdgeID edge = G.get_first_edge(node), + edge_end = G.get_first_invalid_edge(node); + edge < edge_end; ++edge) { + auto const target = G.getEdgeTarget(edge); + if (!G.is_local_node(target)) { + auto const target_owner = G.getTargetPE(target); + if (!std::in_range(target_owner) || + target_owner >= size) { + target_owners_are_valid = false; + continue; + } + auto const owner_index = static_cast(target_owner); + if (!block_incident[owner_index]) { + block_incident[owner_index] = true; + local_volume_is_representable = + checked_add(num_incident_blocks, EdgeWeight{1}) && + local_volume_is_representable; + } } - - return local_comm_vol; + } + local_volume_is_representable = + checked_add(local_comm_vol, num_incident_blocks) && + local_volume_is_representable; + } + require_collective_capacity( + local_volume_is_representable, communicator_view, + "distributed communication volume by rank", + "local communication-volume sum exceeds EdgeWeight capacity"); + require_collective_condition( + target_owners_are_valid, communicator_view, + "distributed communication-volume target owner is outside " + "communicator"); + + auto const local_volume = std::array{local_comm_vol}; + auto total_volume = std::array{}; + auto maximum_volume = std::array{}; + auto minimum_volume = std::array{}; + mpi::all_reduce_checked_sum( + std::span{local_volume}, std::span{total_volume}, + communicator_view, + "MPI_Allreduce(distributed communication volume sum)", + "distributed communication volume by rank", + "global communication-volume sum exceeds EdgeWeight capacity"); + mpi::reduce_bounded(std::span{local_volume}, + std::span{maximum_volume}, mpi::reduction_kind::maximum, + ROOT, communicator_view, + "MPI_Reduce(distributed communication volume maximum)"); + mpi::reduce_bounded(std::span{local_volume}, + std::span{minimum_volume}, mpi::reduction_kind::minimum, + ROOT, communicator_view, + "MPI_Reduce(distributed communication volume minimum)"); + + if (rank == ROOT) { + std::cout << "log> total vol currentdist " << total_volume[0] + << std::endl; + std::cout << "log> max vol currentdist " << maximum_volume[0] + << std::endl; + std::cout << "log> min vol currentdist " << minimum_volume[0] + << std::endl; + if (minimum_volume[0] == 0) { + std::cout << "log> vol dist currentratio undefined" << std::endl; + } else { + std::cout << "log> vol dist currentratio " + << maximum_volume[0] / static_cast(minimum_volume[0]) + << std::endl; + } + } + + return local_comm_vol; + } catch (...) { + mpi::abort_on_exception( + communicator_view.native_handle(), + "distributed communication-volume-by-rank computation failed"); + } } - +} // namespace parhip diff --git a/parallel/parallel_src/lib/tools/distributed_quality_metrics.h b/parallel/parallel_src/lib/tools/distributed_quality_metrics.h index 7b534f1f..02065934 100644 --- a/parallel/parallel_src/lib/tools/distributed_quality_metrics.h +++ b/parallel/parallel_src/lib/tools/distributed_quality_metrics.h @@ -11,23 +11,20 @@ #include "definitions.h" #include "data_structure/parallel_graph_access.h" #include "partition_config.h" - +namespace parhip { class distributed_quality_metrics { public: - distributed_quality_metrics(); - virtual ~distributed_quality_metrics(); - - EdgeWeight local_edge_cut( parallel_graph_access & G, int * partition_map, MPI_Comm communicator ); - EdgeWeight edge_cut( parallel_graph_access & G, MPI_Comm communicator ); - EdgeWeight edge_cut_second( parallel_graph_access & G, MPI_Comm communicator ); - NodeWeight local_max_block_weight( PPartitionConfig & config, parallel_graph_access & G, int * partition_map, MPI_Comm communicator ); - double balance( PPartitionConfig & config, parallel_graph_access & G, MPI_Comm communicator ); - double balance_load( PPartitionConfig & config, parallel_graph_access & G, MPI_Comm communicator ); - double balance_load_dist( PPartitionConfig & config, parallel_graph_access & G, MPI_Comm communicator ); - double balance_second( PPartitionConfig & config, parallel_graph_access & G, MPI_Comm communicator ); + EdgeWeight local_edge_cut( parallel_graph_access & G, int * partition_map, MPI_Comm communicator ); + EdgeWeight edge_cut( parallel_graph_access & G, MPI_Comm communicator ); + EdgeWeight edge_cut_second( parallel_graph_access & G, MPI_Comm communicator ); + NodeWeight local_max_block_weight( PPartitionConfig & config, parallel_graph_access & G, int * partition_map, MPI_Comm communicator ); + double balance( PPartitionConfig & config, parallel_graph_access & G, MPI_Comm communicator ); + double balance_load( PPartitionConfig & config, parallel_graph_access & G, MPI_Comm communicator ); + double balance_load_dist( PPartitionConfig & config, parallel_graph_access & G, MPI_Comm communicator ); + double balance_second( PPartitionConfig & config, parallel_graph_access & G, MPI_Comm communicator ); EdgeWeight comm_vol( PPartitionConfig & config, parallel_graph_access & G, MPI_Comm communicator ); EdgeWeight comm_vol_dist( parallel_graph_access & G, MPI_Comm communicator ); }; - +} #endif /* end of include guard: DISTRIBUTED_QUALITY_METRICS_UAVSEXBT */ diff --git a/parallel/parallel_src/lib/tools/helpers.h b/parallel/parallel_src/lib/tools/helpers.h index 185f413f..f6e9e053 100644 --- a/parallel/parallel_src/lib/tools/helpers.h +++ b/parallel/parallel_src/lib/tools/helpers.h @@ -12,12 +12,11 @@ #include #include #include +#include +namespace parhip { class helpers { public: - helpers() {}; - virtual ~helpers() {}; - template void filter_duplicates( std::vector< vectortype > & input, Compare comparator_function, Equal equal_function); }; @@ -55,5 +54,5 @@ inline bool file_exists(const std::string& name) { return false; } } - +} #endif /* end of include guard: HELPERS_ZUTE7MAJ */ diff --git a/parallel/parallel_src/lib/tools/random_functions.cpp b/parallel/parallel_src/lib/tools/random_functions.cpp deleted file mode 100644 index 46c909bf..00000000 --- a/parallel/parallel_src/lib/tools/random_functions.cpp +++ /dev/null @@ -1,17 +0,0 @@ -/****************************************************************************** - * random_functions.cpp - * * - * Source of KaHIP -- Karlsruhe High Quality Partitioning. - * Christian Schulz - *****************************************************************************/ - -#include "random_functions.h" - -MersenneTwister random_functions::m_mt; -int random_functions::m_seed = 0; - -random_functions::random_functions() { -} - -random_functions::~random_functions() { -} diff --git a/parallel/parallel_src/lib/tools/random_functions.h b/parallel/parallel_src/lib/tools/random_functions.h index 31d1c820..bf3a3bcd 100644 --- a/parallel/parallel_src/lib/tools/random_functions.h +++ b/parallel/parallel_src/lib/tools/random_functions.h @@ -8,159 +8,157 @@ #ifndef RANDOM_FUNCTIONS_RMEPKWYT #define RANDOM_FUNCTIONS_RMEPKWYT -#include #include #include +#include "../../../shared/random_state.h" #include "definitions.h" #include "partition_config.h" +namespace parhip { -typedef std::mt19937 MersenneTwister; +using MersenneTwister = kahip::random_compat::engine_type; class random_functions { - public: - random_functions(); - virtual ~random_functions(); - - template - static void circular_permutation(std::vector & vec) { - if(vec.size() < 2) return; - for( ULONG i = 0; i < vec.size(); i++) { - vec[i] = i; - } - - ULONG size = vec.size(); - std::uniform_int_distribution A(0,size-1); - std::uniform_int_distribution B(0,size-1); - - for( ULONG i = 0; i < size; i++) { - ULONG posA = A(m_mt); - ULONG posB = B(m_mt); - - while(posB == posA) { - posB = B(m_mt); - } - - if( posA != vec[posB] && posB != vec[posA]) { - std::swap(vec[posA], vec[posB]); - } - } - - } - - template - static void permutate_vector_fast(std::vector & vec, bool init) { - if(init) { - for( ULONG i = 0; i < vec.size(); i++) { - vec[i] = i; - } - } - - if(vec.size() < 10) return; - - int distance = 20; - std::uniform_int_distribution A(0, distance); - ULONG size = vec.size()-4; - for( ULONG i = 0; i < size; i++) { - ULONG posA = i; - ULONG posB = (posA + A(m_mt))%size; - std::swap(vec[posA], vec[posB]); - std::swap(vec[posA+1], vec[posB+1]); - std::swap(vec[posA+2], vec[posB+2]); - std::swap(vec[posA+3], vec[posB+3]); - } - } +public: + template + static void circular_permutation(std::vector & vec) { + if(vec.size() < 2) return; + for( ULONG i = 0; i < vec.size(); i++) { + vec[i] = i; + } + + ULONG size = vec.size(); + std::uniform_int_distribution A(0,size-1); + std::uniform_int_distribution B(0,size-1); - template - static void permutate_vector_good(std::vector & vec, bool init) { - if(init) { - for( ULONG i = 0; i < vec.size(); i++) { - vec[i] = (sometype)i; - } - } - - if(vec.size() < 10) { - permutate_vector_good_small(vec); - return; - } - ULONG size = vec.size(); - std::uniform_int_distribution A(0,size - 4); - std::uniform_int_distribution B(0,size - 4); - - for( ULONG i = 0; i < size; i++) { - ULONG posA = A(m_mt); - ULONG posB = B(m_mt); - std::swap(vec[posA], vec[posB]); - std::swap(vec[posA+1], vec[posB+1]); - std::swap(vec[posA+2], vec[posB+2]); - std::swap(vec[posA+3], vec[posB+3]); - - } + for( ULONG i = 0; i < size; i++) { + ULONG posA = A(m_mt); + ULONG posB = B(m_mt); + + while(posB == posA) { + posB = B(m_mt); } - template - static void permutate_vector_good_small(std::vector & vec) { - if(vec.size() < 2) return; - ULONG size = vec.size(); - std::uniform_int_distribution A(0,size-1); - std::uniform_int_distribution B(0,size-1); - - for( ULONG i = 0; i < size; i++) { - ULONG posA = A(m_mt); - ULONG posB = B(m_mt); - std::swap(vec[posA], vec[posB]); - } + if( posA != vec[posB] && posB != vec[posA]) { + std::swap(vec[posA], vec[posB]); } + } - template - static void permutate_entries(const PPartitionConfig & partition_config, - std::vector & vec, - bool init) { - if(init) { - for( ULONG i = 0; i < vec.size(); i++) { - vec[i] = i; - } - } - - switch(partition_config.permutation_quality) { - case PERMUTATION_QUALITY_NONE: break; - case PERMUTATION_QUALITY_FAST: permutate_vector_fast(vec, false); break; - case PERMUTATION_QUALITY_GOOD: permutate_vector_good(vec, false); break; - } + } + template + static void permutate_vector_fast(std::vector & vec, bool init) { + if(init) { + for( ULONG i = 0; i < vec.size(); i++) { + vec[i] = i; } + } - static bool nextBool() { - std::uniform_int_distribution A(0,1); - return (bool) A(m_mt); + if(vec.size() < 10) return; + + int distance = 20; + std::uniform_int_distribution A(0, distance); + ULONG size = vec.size()-4; + for( ULONG i = 0; i < size; i++) { + ULONG posA = i; + ULONG posB = (posA + A(m_mt))%size; + std::swap(vec[posA], vec[posB]); + std::swap(vec[posA+1], vec[posB+1]); + std::swap(vec[posA+2], vec[posB+2]); + std::swap(vec[posA+3], vec[posB+3]); } + } - //including lb and rb - template - static sometype nextInt(sometype lb, sometype rb) { - std::uniform_int_distribution A(lb,rb); - return A(m_mt); + template + static void permutate_vector_good(std::vector & vec, bool init) { + if(init) { + for( ULONG i = 0; i < vec.size(); i++) { + vec[i] = (sometype)i; } + } + if(vec.size() < 10) { + permutate_vector_good_small(vec); + return; + } + ULONG size = vec.size(); + std::uniform_int_distribution A(0,size - 4); + std::uniform_int_distribution B(0,size - 4); + + for( ULONG i = 0; i < size; i++) { + ULONG posA = A(m_mt); + ULONG posB = B(m_mt); + std::swap(vec[posA], vec[posB]); + std::swap(vec[posA+1], vec[posB+1]); + std::swap(vec[posA+2], vec[posB+2]); + std::swap(vec[posA+3], vec[posB+3]); - static double nextDouble(double lb, double rb) { - double rnbr = (double) rand() / (double) RAND_MAX; // rnd in 0,1 - double length = rb - lb; - rnbr *= length; - rnbr += lb; - - return rnbr; + } + } + + template + static void permutate_vector_good_small(std::vector & vec) { + if(vec.size() < 2) return; + ULONG size = vec.size(); + std::uniform_int_distribution A(0,size-1); + std::uniform_int_distribution B(0,size-1); + + for( ULONG i = 0; i < size; i++) { + ULONG posA = A(m_mt); + ULONG posB = B(m_mt); + std::swap(vec[posA], vec[posB]); + } + } + + template + static void permutate_entries(const PPartitionConfig & partition_config, + std::vector & vec, + bool init) { + if(init) { + for( ULONG i = 0; i < vec.size(); i++) { + vec[i] = i; + } } - static void setSeed(int seed) { - m_seed = seed; - srand(seed); - m_mt.seed(m_seed); + switch(partition_config.permutation_quality) { + case PermutationQuality::PERMUTATION_QUALITY_NONE: break; + case PermutationQuality::PERMUTATION_QUALITY_FAST: permutate_vector_fast(vec, false); break; + case PermutationQuality::PERMUTATION_QUALITY_GOOD: permutate_vector_good(vec, false); break; } - private: - static int m_seed; - static MersenneTwister m_mt; -}; + } + + static bool nextBool() { + std::uniform_int_distribution A(0,1); + return static_cast(A(m_mt)); + } + + //including lb and rb + template + static sometype nextInt(sometype lb, sometype rb) { + std::uniform_int_distribution A(lb,rb); + return A(m_mt); + } + + static double nextDouble(double lb, double rb) { + double rnbr = static_cast(rand()) / static_cast(RAND_MAX); + double length = rb - lb; + rnbr *= length; + rnbr += lb; + + return rnbr; + } + + static void setSeed(int seed) { + m_seed = seed; + srand(seed); + m_mt.seed(m_seed); + } + +private: + inline static int& m_seed = kahip::random_compat::seed; + inline static MersenneTwister& m_mt = kahip::random_compat::engine; +}; +} #endif /* end of include guard: RANDOM_FUNCTIONS_RMEPKWYT */ diff --git a/parallel/parallel_src/lib/tools/timer.h b/parallel/parallel_src/lib/tools/timer.h index c5abe388..2c996335 100644 --- a/parallel/parallel_src/lib/tools/timer.h +++ b/parallel/parallel_src/lib/tools/timer.h @@ -8,34 +8,26 @@ #ifndef TIMER_9KPDEP #define TIMER_9KPDEP -#include -#include -#include +#include -class timer { - public: - timer() { - m_start = timestamp(); - } - - void restart() { - m_start = timestamp(); - } +namespace parhip { - double elapsed() { - return timestamp()-m_start; - } +class timer { +public: + timer() : m_start{clock::now()} {} - private: + void restart() { + m_start = clock::now(); + } - /** Returns a timestamp ('now') in seconds (incl. a fractional part). */ - inline double timestamp() { - struct timeval tp; - gettimeofday(&tp, NULL); - return double(tp.tv_sec) + tp.tv_usec / 1000000.; - } + [[nodiscard]] double elapsed() const noexcept { + return std::chrono::duration(clock::now() - m_start).count(); + } - double m_start; -}; +private: + using clock = std::chrono::steady_clock; + std::chrono::time_point m_start; +}; +} #endif /* end of include guard: TIMER_9KPDEP */ diff --git a/parallel/parallel_src/parhip_interface.pc.in b/parallel/parallel_src/parhip_interface.pc.in index 520a9be0..0c331194 100644 --- a/parallel/parallel_src/parhip_interface.pc.in +++ b/parallel/parallel_src/parhip_interface.pc.in @@ -2,7 +2,7 @@ # Copyright (c) 2019 # MIT license (https://opensource.org/license/mit) -prefix=@CMAKE_INSTALL_PREFIX@ +prefix=${pcfiledir}/@KAHIP_PKGCONFIG_PREFIX_FROM_PCFILEDIR@ exec_prefix=${prefix} includedir=${exec_prefix}/@CMAKE_INSTALL_INCLUDEDIR@ libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ diff --git a/parallel/parallel_src/tests/CMakeLists.txt b/parallel/parallel_src/tests/CMakeLists.txt new file mode 100644 index 00000000..9c32dc39 --- /dev/null +++ b/parallel/parallel_src/tests/CMakeLists.txt @@ -0,0 +1,4555 @@ +include(Catch) + +add_library( + kahip_cube_fixture_obj + OBJECT + fixtures/cube_graph.cpp + fixtures/cube_partition.cpp +) +target_sources( + kahip_cube_fixture_obj + PUBLIC + FILE_SET HEADERS + BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}" + FILES + fixtures/cube_graph.h + fixtures/cube_partition.h +) +target_link_libraries( + kahip_cube_fixture_obj + PRIVATE kahip_options kahip_warnings +) + +add_executable(cube_graph_test fixtures/cube_graph_test.cpp) +target_link_libraries( + cube_graph_test + PRIVATE + Catch2::Catch2WithMain + kahip_cube_fixture_obj + kahip_options + kahip_warnings +) +catch_discover_tests( + cube_graph_test + TEST_PREFIX "unit-" + OUTPUT_DIR . + OUTPUT_PREFIX "unit-" +) + +add_executable(cube_partition_test fixtures/cube_partition_test.cpp) +target_link_libraries( + cube_partition_test + PRIVATE + Catch2::Catch2WithMain + kahip_cube_fixture_obj + kahip_options + kahip_warnings +) +catch_discover_tests( + cube_partition_test + TEST_PREFIX "unit-" + OUTPUT_DIR . + OUTPUT_PREFIX "unit-" +) + +add_executable( + cube_scale_probe_core_test + scale/cube_scale_probe_core_test.cpp +) +target_sources( + cube_scale_probe_core_test + PRIVATE + FILE_SET cube_scale_probe_headers + TYPE HEADERS + BASE_DIRS + "${CMAKE_CURRENT_SOURCE_DIR}" + "${PROJECT_SOURCE_DIR}/parallel/shared" + FILES + "${CMAKE_CURRENT_SOURCE_DIR}/scale/cube_scale_probe_core.h" + "${PROJECT_SOURCE_DIR}/parallel/shared/range_owner.h" +) +target_link_libraries( + cube_scale_probe_core_test + PRIVATE Catch2::Catch2WithMain kahip_options kahip_warnings +) +catch_discover_tests( + cube_scale_probe_core_test + TEST_PREFIX "unit-" + OUTPUT_DIR . + OUTPUT_PREFIX "unit-" +) + +add_executable( + bipartition_candidate_test + initial_partitioning/bipartition_candidate_test.cpp +) +target_sources( + bipartition_candidate_test + PRIVATE + FILE_SET bipartition_candidate_headers + TYPE HEADERS + BASE_DIRS "${PROJECT_SOURCE_DIR}/lib" + FILES + "${PROJECT_SOURCE_DIR}/lib/partition/initial_partitioning/bipartition_candidate.h" +) +target_link_libraries( + bipartition_candidate_test + PRIVATE kahip_options kahip_warnings +) +add_test( + NAME unit-bipartition-candidate-ordering + COMMAND $ +) +set_tests_properties( + unit-bipartition-candidate-ordering + PROPERTIES TIMEOUT 5 LABELS "unit;initial-partitioning;mathematics" +) + +add_executable( + root_bipartition_invariant_test + initial_partitioning/root_bipartition_invariant_test.cpp +) +target_sources( + root_bipartition_invariant_test + PRIVATE + FILE_SET bipartition_invariant_headers + TYPE HEADERS + BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/initial_partitioning" + FILES initial_partitioning/bipartition_invariant_cases.h +) +target_link_libraries( + root_bipartition_invariant_test + PRIVATE + Catch2::Catch2WithMain + kahip_core_obj + kahip_options + kahip_warnings +) +catch_discover_tests( + root_bipartition_invariant_test + TEST_PREFIX "unit-" + OUTPUT_DIR . + OUTPUT_PREFIX "unit-" +) + +add_executable( + modified_bipartition_invariant_test + initial_partitioning/modified_bipartition_invariant_test.cpp +) +target_sources( + modified_bipartition_invariant_test + PRIVATE + FILE_SET bipartition_invariant_headers + TYPE HEADERS + BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/initial_partitioning" + FILES initial_partitioning/bipartition_invariant_cases.h +) +target_link_libraries( + modified_bipartition_invariant_test + PRIVATE + Catch2::Catch2WithMain + modified_kahip_core_obj + kahip_options + kahip_warnings +) +catch_discover_tests( + modified_bipartition_invariant_test + TEST_PREFIX "unit-" + OUTPUT_DIR . + OUTPUT_PREFIX "unit-" +) + +add_executable(kahip_cube_generator fixtures/cube_graph_generator.cpp) +target_link_libraries( + kahip_cube_generator + PRIVATE kahip_cube_fixture_obj kahip_options kahip_warnings +) + +add_executable( + kahip_cube_partition_verify + fixtures/cube_partition_verify.cpp +) +target_link_libraries( + kahip_cube_partition_verify + PRIVATE kahip_cube_fixture_obj kahip_options kahip_warnings +) +add_test( + NAME integration-cube-fixture-cli + COMMAND + "${CMAKE_COMMAND}" + "-DGENERATOR=$" + "-DVERIFIER=$" + "-DWORK_DIRECTORY=${CMAKE_CURRENT_BINARY_DIR}/cube-fixture" + -P "${CMAKE_CURRENT_SOURCE_DIR}/fixtures/cube_fixture_cli.cmake" +) +set_tests_properties( + integration-cube-fixture-cli + PROPERTIES TIMEOUT 10 LABELS "integration;fixture" +) + +function( + add_cube_partition_oracle_test + fixture + nx + ny + nz + blocks + ranks + test_labels + test_timeout +) + add_test( + NAME integration-cube-oracle-${fixture}-${ranks}-rank + COMMAND + "${CMAKE_COMMAND}" + "-DGENERATOR=$" + "-DPARHIP=$" + "-DVERIFIER=$" + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DMANIFEST=${CMAKE_CURRENT_SOURCE_DIR}/fixtures/cube_partition_oracle.txt" + "-DWORK_DIRECTORY=${CMAKE_CURRENT_BINARY_DIR}/cube-oracle-${fixture}-${ranks}-rank" + "-DFIXTURE=${fixture}" + "-DNX=${nx}" + "-DNY=${ny}" + "-DNZ=${nz}" + "-DBLOCKS=${blocks}" + "-DRANKS=${ranks}" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/fixtures/cube_partition_oracle.cmake" + ) + set_tests_properties( + integration-cube-oracle-${fixture}-${ranks}-rank + PROPERTIES + PROCESSORS ${ranks} + RUN_SERIAL TRUE + TIMEOUT ${test_timeout} + LABELS "${test_labels}" + ) +endfunction() + +foreach(ranks RANGE 1 5) + add_cube_partition_oracle_test( + cube4 4 4 4 2 ${ranks} "integration;fixture;mpi;oracle" 30 + ) +endforeach() + +add_test( + NAME unit-mpi-tools-profile-aggregate-overflow-aborts-before-payload + COMMAND + "${CMAKE_COMMAND}" + "-DPROBE=$" + -DMODE=profile-aggregate-overflow + -DEXPECT_FAILURE=ON + -DMPI_RANKS=2 + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DEXPECTED_MARKER=mpi-tools-profile-aggregate-overflow affected-communicator; payload all-to-all calls are zero" + "-DEXPECTED_DIAGNOSTIC=ParHIP serial kernel profile failure: reason=collective-aggregate-overflow" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_mpi_fail_fast_probe.cmake" +) +set_tests_properties( + unit-mpi-tools-profile-aggregate-overflow-aborts-before-payload + PROPERTIES PROCESSORS 2 RUN_SERIAL TRUE TIMEOUT 12 +) + +add_executable( + edge_balanced_graph_io_layout_test + io/edge_balanced_graph_io_layout_test.cpp +) +target_link_libraries( + edge_balanced_graph_io_layout_test + PRIVATE libdspac parallel kahip_options kahip_warnings +) +add_test( + NAME unit-edge-balanced-graph-io-layout + COMMAND $ +) +set_tests_properties( + unit-edge-balanced-graph-io-layout + PROPERTIES TIMEOUT 5 LABELS "unit;parallel-io;edge-balanced" +) + +add_executable( + edge_balanced_graph_io_mpi_test + io/edge_balanced_graph_io_mpi_test.cpp +) +target_link_libraries( + edge_balanced_graph_io_mpi_test + PRIVATE + catch_mpi_runner + libdspac + parallel + kahip_options + kahip_warnings +) +add_test( + NAME unit-edge-balanced-graph-io-5-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 5 + ${MPIEXEC_PREFLAGS} + $ + "[edge-balanced]" + ${MPIEXEC_POSTFLAGS} +) +set_tests_properties( + unit-edge-balanced-graph-io-5-rank + PROPERTIES + PROCESSORS 5 + RUN_SERIAL TRUE + TIMEOUT 20 + LABELS "unit;mpi;parallel-io;edge-balanced" +) + +add_executable( + edge_balanced_graph_io_failure_probe + io/edge_balanced_graph_io_failure_probe.cpp +) +target_link_libraries( + edge_balanced_graph_io_failure_probe + PRIVATE libdspac parallel kahip_options kahip_warnings +) +add_test( + NAME unit-pmpi-callback-safety-edge-balanced-graph-io-failure + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/io/edge_balanced_graph_io_failure_probe.cpp" + "-DPROFILE=edge-balanced-graph-io-failure" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_pmpi_callback_safety.cmake" +) +set_tests_properties( + unit-pmpi-callback-safety-edge-balanced-graph-io-failure + PROPERTIES TIMEOUT 5 LABELS "unit;mpi;parallel-io;edge-balanced" +) + +foreach( + failure_mode + IN ITEMS + truncated + nonmonotone + unaligned + wrong-terminal + invalid-target + window-zero +) + if(failure_mode STREQUAL "truncated") + set( + expected_diagnostic + "Distributed backend failure: edge-balanced binary graph has an invalid file extent" + ) + elseif(failure_mode STREQUAL "wrong-terminal") + set( + expected_diagnostic + "Distributed backend failure: edge-balanced binary offset table is invalid" + ) + elseif(failure_mode STREQUAL "invalid-target") + set( + expected_diagnostic + "Distributed backend failure: edge-balanced binary graph payload validation failed" + ) + elseif(failure_mode STREQUAL "window-zero") + set( + expected_diagnostic + "MPI adapter programming failure: edge-balanced graph I/O window must be positive" + ) + else() + set( + expected_diagnostic + "Distributed backend failure: edge-balanced binary graph offset validation failed" + ) + endif() + add_test( + NAME unit-edge-balanced-graph-io-${failure_mode}-failure + COMMAND + "${CMAKE_COMMAND}" + "-DPROBE=$" + "-DMODE=${failure_mode}" + -DEXPECT_FAILURE=ON + -DMPI_RANKS=2 + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DEXPECTED_MARKER=edge-balanced-${failure_mode} affected-communicator; internal MPI_Finalize counter is zero" + "-DEXPECTED_DIAGNOSTIC=${expected_diagnostic}" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_mpi_fail_fast_probe.cmake" + ) + set_tests_properties( + unit-edge-balanced-graph-io-${failure_mode}-failure + PROPERTIES + PROCESSORS 2 + RUN_SERIAL TRUE + TIMEOUT 12 + LABELS "unit;mpi;parallel-io;edge-balanced;failure" + ) +endforeach() + +set( + MODIFIED_EVOLUTIONARY_TEST_HEADERS + "${PROJECT_SOURCE_DIR}/parallel/modified_kahip/app/configuration.h" + "${PROJECT_SOURCE_DIR}/parallel/modified_kahip/lib/data_structure/graph_access.h" + "${PROJECT_SOURCE_DIR}/parallel/modified_kahip/lib/parallel_mh/exchange/exchanger.h" + "${PROJECT_SOURCE_DIR}/parallel/modified_kahip/lib/parallel_mh/parallel_mh_async.h" + "${PROJECT_SOURCE_DIR}/parallel/modified_kahip/lib/parallel_mh/population.h" + "${PROJECT_SOURCE_DIR}/parallel/modified_kahip/lib/tools/quality_metrics.h" + "${PROJECT_SOURCE_DIR}/parallel/modified_kahip/lib/tools/random_functions.h" + "${PROJECT_SOURCE_DIR}/lib/parallel_mh/evolutionary_collectives.h" + "${PROJECT_SOURCE_DIR}/lib/parallel_mh/population_size_broadcast.h" +) + +add_executable( + evolutionary_population_estimate_test + evolutionary/evolutionary_population_estimate_test.cpp +) +target_sources( + evolutionary_population_estimate_test + PRIVATE + FILE_SET evolutionary_population_headers + TYPE HEADERS + BASE_DIRS "${PROJECT_SOURCE_DIR}/lib" + FILES + "${PROJECT_SOURCE_DIR}/lib/parallel_mh/evolutionary_collectives.h" + "${PROJECT_SOURCE_DIR}/lib/parallel_mh/population_size_broadcast.h" +) +target_link_libraries( + evolutionary_population_estimate_test + PRIVATE + Catch2::Catch2WithMain + MPI::MPI_CXX + kahip_fatal_diagnostics + kahip_options + kahip_warnings +) +catch_discover_tests( + evolutionary_population_estimate_test + TEST_PREFIX "unit-" + OUTPUT_DIR . + OUTPUT_PREFIX "unit-" +) + +foreach(evolutionary_target IN ITEMS evolutionary_mpi_test evolutionary_failure_probe) + add_executable( + ${evolutionary_target} + evolutionary/${evolutionary_target}.cpp + ) + target_sources( + ${evolutionary_target} + PRIVATE + FILE_SET modified_evolutionary_headers + TYPE HEADERS + BASE_DIRS + "${PROJECT_SOURCE_DIR}/parallel/modified_kahip/app" + "${PROJECT_SOURCE_DIR}/parallel/modified_kahip/lib" + "${PROJECT_SOURCE_DIR}/lib" + FILES ${MODIFIED_EVOLUTIONARY_TEST_HEADERS} + ) + target_include_directories( + ${evolutionary_target} + PRIVATE + "${PROJECT_SOURCE_DIR}/parallel/modified_kahip/interface" + "${PROJECT_SOURCE_DIR}/parallel/modified_kahip/lib/tools" + "${PROJECT_SOURCE_DIR}/parallel/modified_kahip/lib/partition" + "${PROJECT_SOURCE_DIR}/parallel/modified_kahip/lib/io" + "${PROJECT_SOURCE_DIR}/parallel/modified_kahip/lib/partition/uncoarsening/refinement/quotient_graph_refinement/flow_refinement" + ) + target_link_libraries( + ${evolutionary_target} + PRIVATE + libmodified_kahip_interface + MPI::MPI_CXX + kahip_fatal_diagnostics + kahip_options + kahip_warnings + ) +endforeach() +target_link_libraries(evolutionary_mpi_test PRIVATE catch_mpi_runner) + +add_test( + NAME unit-pmpi-callback-safety-evolutionary-lifetime + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/evolutionary/evolutionary_mpi_test.cpp" + "-DPROFILE=evolutionary-lifetime" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_pmpi_callback_safety.cmake" +) +set_tests_properties( + unit-pmpi-callback-safety-evolutionary-lifetime + PROPERTIES TIMEOUT 5 +) +foreach(mpi_ranks IN ITEMS 2 3 5) + add_test( + NAME unit-evolutionary-lifetime-${mpi_ranks}-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" ${mpi_ranks} + ${MPIEXEC_PREFLAGS} + $ + ${MPIEXEC_POSTFLAGS} + ) + set_tests_properties( + unit-evolutionary-lifetime-${mpi_ranks}-rank + PROPERTIES PROCESSORS ${mpi_ranks} RUN_SERIAL TRUE TIMEOUT 45 + ) +endforeach() + +add_test( + NAME unit-pmpi-callback-safety-evolutionary-lifetime-failure + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/evolutionary/evolutionary_failure_probe.cpp" + "-DPROFILE=evolutionary-lifetime-failure" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_pmpi_callback_safety.cmake" +) +set_tests_properties( + unit-pmpi-callback-safety-evolutionary-lifetime-failure + PROPERTIES TIMEOUT 5 +) +foreach( + failure_mode + IN ITEMS + communicator-duplication + sendrecv + isend + wrong-tag + wrong-count + wait + combine-cross-conditional + invalid-label + weight-overflow + upper-bound-narrowing +) + set(expected_abort_count 2) + if(failure_mode STREQUAL "communicator-duplication") + set( + expected_diagnostic + "MPI backend failure: MPI_Comm_dup(evolutionary communicator)" + ) + elseif(failure_mode STREQUAL "sendrecv") + set( + expected_diagnostic + "MPI backend failure: MPI_Sendrecv(evolutionary permutation exchange)" + ) + elseif(failure_mode STREQUAL "isend") + set( + expected_diagnostic + "MPI backend failure: MPI_Isend(evolutionary rumor)" + ) + elseif(failure_mode STREQUAL "wrong-tag") + set( + expected_diagnostic + "rumor message tag does not match receiver rank" + ) + elseif(failure_mode STREQUAL "wrong-count") + set( + expected_diagnostic + "rumor message count does not match the graph order" + ) + elseif(failure_mode STREQUAL "combine-cross-conditional") + set( + expected_diagnostic + "original-k cross combine is incompatible with asynchronous multi-rank entry" + ) + set(expected_abort_count 1) + elseif(failure_mode STREQUAL "upper-bound-narrowing") + set( + expected_diagnostic + "ParHIP upper bound exceeds the modified KaHIP weight domain" + ) + elseif( + failure_mode STREQUAL "invalid-label" + OR failure_mode STREQUAL "weight-overflow" + ) + set( + expected_diagnostic + "partition labels or block-weight sums exceed their valid domains" + ) + else() + set( + expected_diagnostic + "MPI backend failure: MPI_Wait(evolutionary rumor)" + ) + endif() + add_test( + NAME unit-evolutionary-lifetime-${failure_mode}-failure + COMMAND + "${CMAKE_COMMAND}" + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DPROBE=$" + "-DMODE=${failure_mode}" + "-DEXPECTED_DIAGNOSTIC=${expected_diagnostic}" + "-DEXPECTED_ABORT_COUNT=${expected_abort_count}" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/evolutionary/verify_evolutionary_failure.cmake" + ) + set_tests_properties( + unit-evolutionary-lifetime-${failure_mode}-failure + PROPERTIES PROCESSORS 2 RUN_SERIAL TRUE TIMEOUT 20 + ) +endforeach() + +foreach( + root_evolutionary_target + IN ITEMS root_evolutionary_mpi_test root_evolutionary_failure_probe +) + add_executable( + ${root_evolutionary_target} + evolutionary/${root_evolutionary_target}.cpp + ) + target_link_libraries( + ${root_evolutionary_target} + PRIVATE + kahip_core_obj + kahip_mapping_obj + kahip_collective_obj + MPI::MPI_CXX + kahip_fatal_diagnostics + kahip_options + kahip_warnings + ) +endforeach() +target_link_libraries(root_evolutionary_mpi_test PRIVATE catch_mpi_runner) + +add_test( + NAME unit-pmpi-callback-safety-root-evolutionary-lifetime + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/evolutionary/root_evolutionary_mpi_test.cpp" + "-DPROFILE=evolutionary-lifetime" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_pmpi_callback_safety.cmake" +) +set_tests_properties( + unit-pmpi-callback-safety-root-evolutionary-lifetime + PROPERTIES TIMEOUT 5 LABELS "unit;mpi;evolutionary;lifetime" +) +foreach(mpi_ranks IN ITEMS 2 3 5) + add_test( + NAME unit-root-evolutionary-lifetime-${mpi_ranks}-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" ${mpi_ranks} + ${MPIEXEC_PREFLAGS} + $ + ${MPIEXEC_POSTFLAGS} + ) + set_tests_properties( + unit-root-evolutionary-lifetime-${mpi_ranks}-rank + PROPERTIES + PROCESSORS ${mpi_ranks} + RUN_SERIAL TRUE + TIMEOUT 45 + LABELS "unit;mpi;evolutionary;lifetime" + ) +endforeach() + +add_test( + NAME unit-pmpi-callback-safety-root-evolutionary-lifetime-failure + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/evolutionary/root_evolutionary_failure_probe.cpp" + "-DPROFILE=evolutionary-lifetime-failure" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_pmpi_callback_safety.cmake" +) +set_tests_properties( + unit-pmpi-callback-safety-root-evolutionary-lifetime-failure + PROPERTIES TIMEOUT 5 LABELS "unit;mpi;evolutionary;lifetime;failure" +) +foreach( + failure_mode + IN ITEMS + communicator-duplication + sendrecv + isend + wrong-tag + wrong-count + wait + unfinished-teardown + combine-cross-conditional + invalid-label + weight-overflow +) + set(expected_abort_count 2) + if(failure_mode STREQUAL "communicator-duplication") + set( + expected_diagnostic + "MPI backend failure: MPI_Comm_dup(evolutionary communicator)" + ) + elseif(failure_mode STREQUAL "sendrecv") + set( + expected_diagnostic + "MPI backend failure: MPI_Sendrecv(evolutionary permutation exchange)" + ) + elseif(failure_mode STREQUAL "isend") + set( + expected_diagnostic + "MPI backend failure: MPI_Isend(evolutionary rumor)" + ) + elseif(failure_mode STREQUAL "wrong-tag") + set( + expected_diagnostic + "rumor message tag does not match receiver rank" + ) + elseif(failure_mode STREQUAL "wrong-count") + set( + expected_diagnostic + "rumor message count does not match the graph order" + ) + elseif(failure_mode STREQUAL "unfinished-teardown") + set( + expected_diagnostic + "MPI evolutionary collective failure in evolutionary rumor exchange teardown on rank" + ) + elseif(failure_mode STREQUAL "combine-cross-conditional") + set( + expected_diagnostic + "original-k cross combine is incompatible with asynchronous multi-rank entry" + ) + set(expected_abort_count 1) + elseif( + failure_mode STREQUAL "invalid-label" + OR failure_mode STREQUAL "weight-overflow" + ) + set( + expected_diagnostic + "partition labels or block-weight sums exceed their valid domains" + ) + else() + set( + expected_diagnostic + "MPI backend failure: MPI_Wait(evolutionary rumor)" + ) + endif() + add_test( + NAME unit-root-evolutionary-lifetime-${failure_mode}-failure + COMMAND + "${CMAKE_COMMAND}" + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DPROBE=$" + "-DMODE=${failure_mode}" + "-DEXPECTED_DIAGNOSTIC=${expected_diagnostic}" + "-DEXPECTED_ABORT_COUNT=${expected_abort_count}" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/evolutionary/verify_evolutionary_failure.cmake" + ) + set_tests_properties( + unit-root-evolutionary-lifetime-${failure_mode}-failure + PROPERTIES + PROCESSORS 2 + RUN_SERIAL TRUE + TIMEOUT 20 + LABELS "unit;mpi;evolutionary;lifetime;failure" + ) +endforeach() + +add_executable(application_math_test app/application_math_test.cpp) +target_link_libraries( + application_math_test + PRIVATE kahip_options kahip_warnings +) +target_include_directories( + application_math_test + PRIVATE "${PROJECT_SOURCE_DIR}/parallel/shared" +) +add_test(NAME unit-application-math COMMAND application_math_test) +set_tests_properties(unit-application-math PROPERTIES LABELS "unit;app") + +add_executable( + mpi_application_runtime_mpi_test + app/mpi_application_runtime_mpi_test.cpp +) +target_link_libraries( + mpi_application_runtime_mpi_test + PRIVATE + parhip_mpi_application_obj + parallel + kahip_options + kahip_warnings +) +foreach(mpi_ranks IN ITEMS 1 2) + add_test( + NAME unit-mpi-application-runtime-${mpi_ranks}-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" ${mpi_ranks} + ${MPIEXEC_PREFLAGS} + $ + ${MPIEXEC_POSTFLAGS} + ) + set_tests_properties( + unit-mpi-application-runtime-${mpi_ranks}-rank + PROPERTIES + PROCESSORS ${mpi_ranks} + RUN_SERIAL TRUE + TIMEOUT 10 + LABELS "unit;app;mpi;lifecycle" + ) +endforeach() + +add_executable( + parser_communicator_mpi_test + app/parser_communicator_mpi_test.cpp +) +target_compile_definitions( + parser_communicator_mpi_test + PRIVATE PARALLEL_LABEL_COMPRESSION +) +target_link_libraries( + parser_communicator_mpi_test + PRIVATE parallel argtable3 kahip_options kahip_warnings +) +add_test( + NAME unit-parser-explicit-communicator-2-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 2 + ${MPIEXEC_PREFLAGS} + $ + ${MPIEXEC_POSTFLAGS} +) +set_tests_properties( + unit-parser-explicit-communicator-2-rank + PROPERTIES + PROCESSORS 2 + RUN_SERIAL TRUE + TIMEOUT 10 + LABELS "unit;app;mpi;parser" +) + +add_executable( + mpi_application_runtime_failure_probe + app/mpi_application_runtime_failure_probe.cpp +) +target_link_libraries( + mpi_application_runtime_failure_probe + PRIVATE + parhip_mpi_application_obj + parallel + kahip_fatal_diagnostics + kahip_options + kahip_warnings +) +foreach( + runtime_failure_mode + IN ITEMS + initialization + finalization + operation-exception + backend-error +) + if(runtime_failure_mode STREQUAL "initialization") + set( + runtime_failure_diagnostic + "MPI lifecycle failure: runtime failure probe: MPI_Init returned raw error 17301 \\(rank unavailable\\)" + ) + set(runtime_failure_marker "observed process abort after diagnostic flush") + elseif(runtime_failure_mode STREQUAL "finalization") + set( + runtime_failure_diagnostic + "MPI lifecycle failure: runtime failure probe: MPI_Finalize returned raw error 17302 on rank 0" + ) + set(runtime_failure_marker "observed process abort after diagnostic flush") + elseif(runtime_failure_mode STREQUAL "operation-exception") + set( + runtime_failure_diagnostic + "runtime failure probe: injected operation exception \\(rank 0\\)" + ) + set( + runtime_failure_marker + "observed operation communicator MPI_Abort after diagnostic flush" + ) + else() + set( + runtime_failure_diagnostic + "MPI backend failure: injected application backend error.*world rank 0" + ) + set( + runtime_failure_marker + "observed operation communicator MPI_Abort after diagnostic flush" + ) + endif() + add_test( + NAME unit-mpi-application-${runtime_failure_mode}-failure + COMMAND + "${CMAKE_COMMAND}" + "-DPROBE=$" + "-DMODE=${runtime_failure_mode}" + "-DEXPECTED_DIAGNOSTIC=${runtime_failure_diagnostic}" + "-DEXPECTED_MARKER=${runtime_failure_marker}" + -P "${CMAKE_CURRENT_SOURCE_DIR}/app/verify_failure_probe.cmake" + ) + set_tests_properties( + unit-mpi-application-${runtime_failure_mode}-failure + PROPERTIES + RUN_SERIAL TRUE + TIMEOUT 10 + LABELS "unit;app;mpi;lifecycle;failure" + ) +endforeach() + +add_executable( + mpi_owned_handle_lifetime_failure_probe + app/mpi_owned_handle_lifetime_failure_probe.cpp +) +target_link_libraries( + mpi_owned_handle_lifetime_failure_probe + PRIVATE parallel kahip_fatal_diagnostics kahip_options kahip_warnings +) +foreach(owned_handle IN ITEMS communicator datatype distributed-graph) + if(owned_handle STREQUAL "communicator") + set(owned_handle_diagnostic "communicator destruction") + elseif(owned_handle STREQUAL "datatype") + set(owned_handle_diagnostic "owned datatype destruction") + else() + set(owned_handle_diagnostic "distributed graph destruction") + endif() + add_test( + NAME unit-mpi-owned-${owned_handle}-outlives-runtime + COMMAND + "${CMAKE_COMMAND}" + "-DPROBE=$" + "-DMODE=${owned_handle}" + "-DEXPECTED_DIAGNOSTIC=MPI adapter ownership outlived the active MPI runtime: ${owned_handle_diagnostic}" + "-DEXPECTED_MARKER=observed owned-handle abort after diagnostic flush" + -P "${CMAKE_CURRENT_SOURCE_DIR}/app/verify_failure_probe.cmake" + ) + set_tests_properties( + unit-mpi-owned-${owned_handle}-outlives-runtime + PROPERTIES + RUN_SERIAL TRUE + TIMEOUT 10 + LABELS "unit;app;mpi;lifecycle;failure" + ) +endforeach() + +if(UNIX AND NOT APPLE) + add_library(mpi_finalize_observer SHARED app/mpi_finalize_observer.cpp) + target_link_libraries( + mpi_finalize_observer + PRIVATE MPI::MPI_CXX kahip_options kahip_warnings + ) + add_test( + NAME integration-kaffpae-help-finalizes-mpi + COMMAND + "${CMAKE_COMMAND}" + "-DEXECUTABLE=$" + "-DOBSERVER=$" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/app/verify_executable_finalizes.cmake" + ) + set_tests_properties( + integration-kaffpae-help-finalizes-mpi + PROPERTIES + RUN_SERIAL TRUE + TIMEOUT 10 + LABELS "integration;app;mpi;lifecycle" + ) +endif() + +add_test( + NAME integration-kaffpae-invalid-k + COMMAND + "${CMAKE_COMMAND}" + "-DKAFFPAE=$" + -P "${CMAKE_CURRENT_SOURCE_DIR}/app/verify_kaffpae_invalid_k.cmake" +) +set_tests_properties( + integration-kaffpae-invalid-k + PROPERTIES + RUN_SERIAL TRUE + TIMEOUT 10 + LABELS "integration;app;mpi;validation" +) + +add_executable( + kaffpae_runtime_failure_probe + app/kaffpae_runtime_failure_probe.cpp + "${PROJECT_SOURCE_DIR}/app/mpi_application_runtime.cpp" +) +target_sources( + kaffpae_runtime_failure_probe + PRIVATE + FILE_SET root_mpi_runtime_headers + TYPE HEADERS + BASE_DIRS "${PROJECT_SOURCE_DIR}/app" + FILES "${PROJECT_SOURCE_DIR}/app/mpi_application_runtime.h" +) +target_link_libraries( + kaffpae_runtime_failure_probe + PRIVATE + MPI::MPI_CXX + kahip_fatal_diagnostics + kahip_options + kahip_warnings +) +add_test( + NAME unit-pmpi-callback-safety-kaffpae-runtime-failure + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/app/kaffpae_runtime_failure_probe.cpp" + "-DPROFILE=kaffpae-runtime-failure" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_pmpi_callback_safety.cmake" +) +set_tests_properties( + unit-pmpi-callback-safety-kaffpae-runtime-failure + PROPERTIES TIMEOUT 5 LABELS "unit;app;mpi;lifecycle;failure" +) +foreach(runtime_failure_mode IN ITEMS rank-query communicator-free) + if(runtime_failure_mode STREQUAL "rank-query") + set( + runtime_failure_diagnostic + "MPI backend failure: root runtime failure probe: MPI_Comm_rank\\(kaffpaE\\) returned raw error 17401 on rank 0" + ) + set( + runtime_failure_marker + "observed operation communicator MPI_Abort after diagnostic flush" + ) + else() + set( + runtime_failure_diagnostic + "MPI backend failure: root runtime failure probe: MPI_Comm_free\\(application operation communicator\\) returned raw error 17402 on rank 0" + ) + set( + runtime_failure_marker + "observed MPI_COMM_WORLD fallback abort after diagnostic flush" + ) + endif() + add_test( + NAME unit-kaffpae-runtime-${runtime_failure_mode}-failure + COMMAND + "${CMAKE_COMMAND}" + "-DPROBE=$" + "-DMODE=${runtime_failure_mode}" + "-DEXPECTED_DIAGNOSTIC=${runtime_failure_diagnostic}" + "-DEXPECTED_MARKER=${runtime_failure_marker}" + -P "${CMAKE_CURRENT_SOURCE_DIR}/app/verify_failure_probe.cmake" + ) + set_tests_properties( + unit-kaffpae-runtime-${runtime_failure_mode}-failure + PROPERTIES + RUN_SERIAL TRUE + TIMEOUT 10 + LABELS "unit;app;mpi;lifecycle;failure" + ) +endforeach() + +add_test( + NAME integration-parhip-invalid-k + COMMAND + "${CMAKE_COMMAND}" + "-DPARHIP=$" + -P "${CMAKE_CURRENT_SOURCE_DIR}/app/verify_parhip_invalid_k.cmake" +) +set_tests_properties( + integration-parhip-invalid-k + PROPERTIES + RUN_SERIAL TRUE + TIMEOUT 10 + LABELS "integration;app;mpi;validation" +) +foreach(ranks IN ITEMS 2 4) + add_cube_partition_oracle_test( + cube10 10 10 10 4 ${ranks} "integration;fixture;mpi;oracle" 30 + ) + add_cube_partition_oracle_test( + cube100 100 100 100 4 ${ranks} + "integration;fixture;mpi;oracle;large" 120 + ) +endforeach() + +foreach( + validation_case + IN ITEMS + malformed-provenance + duplicate-provenance + missing-provenance + unknown-provenance + invalid-compiler + invalid-mpi + invalid-cell-id + invalid-adjacency + invalid-preconfiguration + invalid-seed + invalid-imbalance + exact-cut + orphaned-repair-provenance + missing-repair-provenance + invalid-repair-provenance + mismatched-repair-upstream-anchor +) + add_test( + NAME unit-cube-oracle-validation-${validation_case} + COMMAND + "${CMAKE_COMMAND}" + "-DTEST_CASE=${validation_case}" + "-DORACLE_SCRIPT=${CMAKE_CURRENT_SOURCE_DIR}/fixtures/cube_partition_oracle.cmake" + "-DMANIFEST=${CMAKE_CURRENT_SOURCE_DIR}/fixtures/cube_partition_oracle.txt" + "-DGENERATOR=$" + "-DPARHIP=$" + "-DVERIFIER=$" + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DWORK_DIRECTORY=${CMAKE_CURRENT_BINARY_DIR}/cube-oracle-validation/${validation_case}" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/fixtures/cube_partition_oracle_validation_test.cmake" + ) + set_tests_properties( + unit-cube-oracle-validation-${validation_case} + PROPERTIES + PROCESSORS 1 + RUN_SERIAL TRUE + TIMEOUT 15 + LABELS "oracle;fixture" + ) +endforeach() + +add_executable(random_functions_test tools/random_functions_test.cpp) +target_link_libraries( + random_functions_test + PRIVATE Catch2::Catch2WithMain parallel kahip_options kahip_warnings +) +catch_discover_tests( + random_functions_test + TEST_PREFIX "unit-" + OUTPUT_DIR . + OUTPUT_PREFIX "unit-" +) + +add_executable( + partition_config_test + distributed_partitioning/partition_config_test.cpp +) +target_link_libraries( + partition_config_test + PRIVATE Catch2::Catch2WithMain parallel kahip_options kahip_warnings +) +catch_discover_tests( + partition_config_test + TEST_PREFIX "unit-" + OUTPUT_DIR . + OUTPUT_PREFIX "unit-" +) + +# MPI Version of Catch Test Runner +add_library(catch_mpi_runner catch_mpi/catch_mpi_runner.cpp) +target_link_libraries(catch_mpi_runner PUBLIC MPI::MPI_CXX) +target_link_libraries(catch_mpi_runner PUBLIC Catch2::Catch2) +target_link_libraries(catch_mpi_runner PRIVATE kahip_options) + +add_executable( + random_initial_partitioning_mpi_test + distributed_partitioning/random_initial_partitioning_mpi_test.cpp +) +target_link_libraries( + random_initial_partitioning_mpi_test + PRIVATE + catch_mpi_runner + parallel + kahip_options + kahip_warnings +) +foreach(mpi_ranks RANGE 1 5) + add_test( + NAME unit-random-initial-partitioning-${mpi_ranks}-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" ${mpi_ranks} + ${MPIEXEC_PREFLAGS} + $ + ${MPIEXEC_POSTFLAGS} + ) + set_tests_properties( + unit-random-initial-partitioning-${mpi_ranks}-rank + PROPERTIES + PROCESSORS ${mpi_ranks} + RUN_SERIAL TRUE + TIMEOUT 15 + LABELS "unit;mpi;distributed-partitioning;random" + ) +endforeach() + +add_executable( + distributed_partitioner_failure_probe + distributed_partitioning/distributed_partitioner_failure_probe.cpp +) +target_link_libraries( + distributed_partitioner_failure_probe + PRIVATE parallel kahip_options kahip_warnings +) +add_test( + NAME unit-pmpi-callback-safety-distributed-partitioner-failure + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/distributed_partitioning/distributed_partitioner_failure_probe.cpp" + "-DPROFILE=distributed-partitioner-failure" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_pmpi_callback_safety.cmake" +) +set_tests_properties( + unit-pmpi-callback-safety-distributed-partitioner-failure + PROPERTIES TIMEOUT 5 LABELS "unit;mpi;distributed-partitioning;failure" +) +foreach( + failure_mode + IN ITEMS + zero-k + mismatched-k + zero-cluster-factor + infinite-cluster-factor + negative-choice-count + choice-capacity + exhausted-choice-cursor + rank-backend + mismatched-communicator +) + if(failure_mode STREQUAL "zero-k") + set( + expected_diagnostic + "random initial partitioning requires k greater than zero" + ) + elseif(failure_mode STREQUAL "mismatched-k") + set( + expected_diagnostic + "random initial partitioning k differs across communicator" + ) + elseif( + failure_mode STREQUAL "zero-cluster-factor" + OR failure_mode STREQUAL "infinite-cluster-factor" + ) + set( + expected_diagnostic + "distributed partitioner requires a finite positive cluster coarsening factor" + ) + elseif(failure_mode STREQUAL "negative-choice-count") + set( + expected_diagnostic + "distributed partitioner random-choice counts must be nonnegative" + ) + elseif(failure_mode STREQUAL "choice-capacity") + set( + expected_diagnostic + "random-choice count exceeds addressable vector capacity" + ) + elseif(failure_mode STREQUAL "exhausted-choice-cursor") + set( + expected_diagnostic + "distributed partitioning random-choice cursor exceeds generated choices" + ) + elseif(failure_mode STREQUAL "rank-backend") + set(expected_diagnostic "MPI backend failure: MPI_Comm_rank") + else() + set( + expected_diagnostic + "random initial partitioning graph communicator differs in process or rank order" + ) + endif() + add_test( + NAME unit-distributed-partitioner-${failure_mode}-failure + COMMAND + "${CMAKE_COMMAND}" + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DPROBE=$" + "-DMODE=${failure_mode}" + "-DEXPECTED_DIAGNOSTIC=${expected_diagnostic}" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/distributed_partitioning/verify_distributed_partitioner_failure.cmake" + ) + set_tests_properties( + unit-distributed-partitioner-${failure_mode}-failure + PROPERTIES + PROCESSORS 2 + RUN_SERIAL TRUE + TIMEOUT 20 + LABELS "unit;mpi;distributed-partitioning;failure" + ) +endforeach() + +# Parallel contraction +add_executable( + parallel_contraction_test + parallel_contraction/parallel_contraction_test.cpp +) +target_link_libraries( + parallel_contraction_test + PRIVATE Catch2::Catch2WithMain parallel +) +target_link_libraries( + parallel_contraction_test + PRIVATE libmodified_kahip_interface +) + +catch_discover_tests( + parallel_contraction_test + TEST_PREFIX "unit-" + OUTPUT_DIR . + OUTPUT_PREFIX "unit-" +) + +# Parallel contraction +add_executable( + parallel_contraction_mpi_test + parallel_contraction/parallel_contraction_mpi_test.cpp +) +target_link_libraries(parallel_contraction_mpi_test PRIVATE catch_mpi_runner) +target_link_libraries(parallel_contraction_mpi_test PRIVATE parallel) +target_link_libraries( + parallel_contraction_mpi_test + PRIVATE libmodified_kahip_interface +) + +foreach(mpi_ranks RANGE 1 5) + add_test( + NAME unit-parallel-contraction-mpi-${mpi_ranks}-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" ${mpi_ranks} + ${MPIEXEC_PREFLAGS} + $ + ${MPIEXEC_POSTFLAGS} + ) + set_tests_properties( + unit-parallel-contraction-mpi-${mpi_ranks}-rank + PROPERTIES + PROCESSORS ${mpi_ranks} + RUN_SERIAL TRUE + TIMEOUT 10 + ) +endforeach() + +add_executable( + distributed_quality_metrics_mpi_test + tools/distributed_quality_metrics_mpi_test.cpp +) +target_link_libraries( + distributed_quality_metrics_mpi_test + PRIVATE + catch_mpi_runner + parallel + kahip_options + kahip_warnings +) +add_test( + NAME unit-pmpi-callback-safety-quality-metrics + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/tools/distributed_quality_metrics_mpi_test.cpp" + "-DPROFILE=quality-metrics" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_pmpi_callback_safety.cmake" +) +set_tests_properties( + unit-pmpi-callback-safety-quality-metrics + PROPERTIES TIMEOUT 5 +) +foreach(mpi_ranks RANGE 1 5) + add_test( + NAME unit-distributed-quality-metrics-${mpi_ranks}-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" ${mpi_ranks} + ${MPIEXEC_PREFLAGS} + $ + ${MPIEXEC_POSTFLAGS} + ) + set_tests_properties( + unit-distributed-quality-metrics-${mpi_ranks}-rank + PROPERTIES PROCESSORS ${mpi_ranks} RUN_SERIAL TRUE TIMEOUT 12 + ) +endforeach() + +add_executable( + distributed_quality_metrics_failure_probe + tools/distributed_quality_metrics_failure_probe.cpp +) +target_link_libraries( + distributed_quality_metrics_failure_probe + PRIVATE parallel kahip_options kahip_warnings +) +add_test( + NAME unit-pmpi-callback-safety-quality-metrics-failure + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/tools/distributed_quality_metrics_failure_probe.cpp" + "-DPROFILE=quality-metrics-failure" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_pmpi_callback_safety.cmake" +) +set_tests_properties( + unit-pmpi-callback-safety-quality-metrics-failure + PROPERTIES TIMEOUT 5 +) +foreach( + failure_mode + IN ITEMS + backend + zero-k + invalid-label + invalid-second-label + invalid-load-label + mismatched-k + local-overflow + global-overflow + load-overflow + edge-cut-local-overflow + edge-cut-second-local-overflow + local-edge-cut-overflow + edge-cut-global-overflow + edge-cut-second-global-overflow + communication-volume-total-overflow + distribution-volume-global-overflow + null-partition-map +) + if(failure_mode STREQUAL "backend") + set( + expected_diagnostic + "MPI backend failure: MPI_Allreduce(distributed block weights)" + ) + elseif(failure_mode STREQUAL "zero-k") + set( + expected_diagnostic + "MPI adapter programming failure: distributed balance requires k greater than zero" + ) + elseif(failure_mode STREQUAL "invalid-label") + set( + expected_diagnostic + "MPI adapter programming failure: distributed balance label is outside [0, k)" + ) + elseif(failure_mode STREQUAL "invalid-second-label") + set( + expected_diagnostic + "MPI adapter programming failure: distributed second balance label is outside [0, k)" + ) + elseif(failure_mode STREQUAL "invalid-load-label") + set( + expected_diagnostic + "MPI adapter programming failure: distributed load balance label is outside [0, k)" + ) + elseif(failure_mode STREQUAL "mismatched-k") + set( + expected_diagnostic + "MPI adapter programming failure: distributed balance k differs across communicator" + ) + elseif(failure_mode STREQUAL "local-overflow") + set( + expected_diagnostic + "MPI adapter capacity failure: distributed balance: local vertex-weight sum exceeds NodeWeight capacity" + ) + elseif(failure_mode STREQUAL "global-overflow") + set( + expected_diagnostic + "MPI adapter capacity failure: distributed balance: global block-weight sum exceeds NodeWeight capacity" + ) + elseif(failure_mode STREQUAL "load-overflow") + set( + expected_diagnostic + "MPI adapter capacity failure: distributed load balance: local node-load sum exceeds NodeWeight capacity" + ) + elseif(failure_mode STREQUAL "edge-cut-local-overflow") + set( + expected_diagnostic + "MPI adapter capacity failure: distributed edge cut: local edge-cut sum exceeds EdgeWeight capacity" + ) + elseif(failure_mode STREQUAL "edge-cut-second-local-overflow") + set( + expected_diagnostic + "MPI adapter capacity failure: distributed second edge cut: local edge-cut sum exceeds EdgeWeight capacity" + ) + elseif(failure_mode STREQUAL "local-edge-cut-overflow") + set( + expected_diagnostic + "MPI adapter capacity failure: local edge cut: local edge-cut sum exceeds EdgeWeight capacity" + ) + elseif(failure_mode STREQUAL "edge-cut-global-overflow") + set( + expected_diagnostic + "MPI adapter capacity failure: distributed edge cut: global edge-cut sum exceeds EdgeWeight capacity" + ) + elseif(failure_mode STREQUAL "edge-cut-second-global-overflow") + set( + expected_diagnostic + "MPI adapter capacity failure: distributed second edge cut: global edge-cut sum exceeds EdgeWeight capacity" + ) + elseif(failure_mode STREQUAL "communication-volume-total-overflow") + set( + expected_diagnostic + "MPI adapter capacity failure: distributed communication volume: total communication volume exceeds EdgeWeight capacity" + ) + elseif(failure_mode STREQUAL "distribution-volume-global-overflow") + set( + expected_diagnostic + "MPI adapter capacity failure: distributed communication volume by rank: global communication-volume sum exceeds EdgeWeight capacity" + ) + else() + set( + expected_diagnostic + "MPI adapter programming failure: local maximum block weight requires a partition map for nonempty local storage" + ) + endif() + add_test( + NAME unit-distributed-quality-metrics-${failure_mode}-failure + COMMAND + "${CMAKE_COMMAND}" + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DPROBE=$" + "-DMODE=${failure_mode}" + "-DEXPECTED_DIAGNOSTIC=${expected_diagnostic}" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/tools/verify_distributed_quality_metrics_failure.cmake" + ) + set_tests_properties( + unit-distributed-quality-metrics-${failure_mode}-failure + PROPERTIES PROCESSORS 3 RUN_SERIAL TRUE TIMEOUT 12 + ) +endforeach() + +add_executable( + balance_management_refinement_failure_probe + tools/balance_management_refinement_failure_probe.cpp +) +target_link_libraries( + balance_management_refinement_failure_probe + PRIVATE parallel kahip_options kahip_warnings +) +add_test( + NAME unit-pmpi-callback-safety-balance-refinement-failure + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/tools/balance_management_refinement_failure_probe.cpp" + "-DPROFILE=balance-refinement-failure" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_pmpi_callback_safety.cmake" +) +set_tests_properties( + unit-pmpi-callback-safety-balance-refinement-failure + PROPERTIES TIMEOUT 5 +) +foreach( + failure_mode + IN ITEMS backend zero-k invalid-label local-overflow global-overflow +) + if(failure_mode STREQUAL "backend") + set( + expected_diagnostic + "MPI backend failure: MPI_Allreduce(refinement block weights)" + ) + elseif(failure_mode STREQUAL "zero-k") + set( + expected_diagnostic + "MPI adapter programming failure: refinement balance management requires at least one block" + ) + elseif(failure_mode STREQUAL "invalid-label") + set( + expected_diagnostic + "MPI adapter programming failure: refinement balance-management label is outside [0, k)" + ) + elseif(failure_mode STREQUAL "local-overflow") + set( + expected_diagnostic + "MPI adapter capacity failure: refinement balance management: local block-weight sum exceeds NodeWeight capacity" + ) + else() + set( + expected_diagnostic + "MPI adapter capacity failure: refinement balance management: global block-weight sum exceeds NodeWeight capacity" + ) + endif() + add_test( + NAME unit-balance-refinement-${failure_mode}-failure + COMMAND + "${CMAKE_COMMAND}" + "-DPROBE=$" + "-DMODE=${failure_mode}" + -DEXPECT_FAILURE=ON + -DMPI_RANKS=3 + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DEXPECTED_MARKER=balance-refinement affected-communicator; internal MPI_Finalize counter is zero" + "-DEXPECTED_DIAGNOSTIC=${expected_diagnostic}" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_mpi_fail_fast_probe.cmake" + ) + set_tests_properties( + unit-balance-refinement-${failure_mode}-failure + PROPERTIES PROCESSORS 3 RUN_SERIAL TRUE TIMEOUT 12 + ) +endforeach() + +add_test( + NAME unit-parallel-ghost-cnode-transaction-3-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 3 + ${MPIEXEC_PREFLAGS} + $ + "ghost CNode receive failures preserve the complete mapping and trace" + ${MPIEXEC_POSTFLAGS} +) +set_tests_properties( + unit-parallel-ghost-cnode-transaction-3-rank + PROPERTIES PROCESSORS 3 RUN_SERIAL TRUE TIMEOUT 20 +) + +add_test( + NAME unit-parallel-ghost-cnode-zero-domain-3-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 3 + ${MPIEXEC_PREFLAGS} + $ + "zero coarse domain with local work fails before sparse payload" + ${MPIEXEC_POSTFLAGS} +) +set_tests_properties( + unit-parallel-ghost-cnode-zero-domain-3-rank + PROPERTIES PROCESSORS 3 RUN_SERIAL TRUE TIMEOUT 10 +) + +add_test( + NAME unit-parallel-ghost-cnode-asymmetric-2-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 2 + ${MPIEXEC_PREFLAGS} + $ + "asymmetric ghost topology fails commonly before payload" + ${MPIEXEC_POSTFLAGS} +) +set_tests_properties( + unit-parallel-ghost-cnode-asymmetric-2-rank + PROPERTIES PROCESSORS 2 RUN_SERIAL TRUE TIMEOUT 10 +) + +add_test( + NAME unit-parallel-ghost-weight-transaction-3-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 3 + ${MPIEXEC_PREFLAGS} + $ + "ghost weight receive failures preserve every weight and trace" + ${MPIEXEC_POSTFLAGS} +) +set_tests_properties( + unit-parallel-ghost-weight-transaction-3-rank + PROPERTIES PROCESSORS 3 RUN_SERIAL TRUE TIMEOUT 20 +) + +add_test( + NAME unit-parallel-ghost-weight-asymmetric-2-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 2 + ${MPIEXEC_PREFLAGS} + $ + "asymmetric ghost-weight topology fails commonly before payload" + ${MPIEXEC_POSTFLAGS} +) +set_tests_properties( + unit-parallel-ghost-weight-asymmetric-2-rank + PROPERTIES PROCESSORS 2 RUN_SERIAL TRUE TIMEOUT 10 +) + +add_test( + NAME unit-parallel-ghost-weight-reuse-2-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 2 + ${MPIEXEC_PREFLAGS} + $ + "ghost weight exchange reuses a prewarmed plan and refreshes values" + ${MPIEXEC_POSTFLAGS} +) +set_tests_properties( + unit-parallel-ghost-weight-reuse-2-rank + PROPERTIES PROCESSORS 2 RUN_SERIAL TRUE TIMEOUT 10 +) + +add_test( + NAME unit-parallel-quotient-arithmetic-3-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 3 + ${MPIEXEC_PREFLAGS} + $ + "[arithmetic]" + ${MPIEXEC_POSTFLAGS} +) +set_tests_properties( + unit-parallel-quotient-arithmetic-3-rank + PROPERTIES PROCESSORS 3 RUN_SERIAL TRUE TIMEOUT 15 +) + +add_test( + NAME unit-parallel-label-invalid-3-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 3 + ${MPIEXEC_PREFLAGS} + $ + "label mapping rejects an out-of-domain local label collectively" + ${MPIEXEC_POSTFLAGS} +) + +add_test( + NAME unit-parallel-label-domain-skew-3-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 3 + ${MPIEXEC_PREFLAGS} + $ + "label mapping rejects an empty-payload global-count mismatch collectively" + ${MPIEXEC_POSTFLAGS} +) +set_tests_properties( + unit-parallel-label-domain-skew-3-rank + PROPERTIES + PROCESSORS 3 + RUN_SERIAL TRUE + TIMEOUT 5 +) +set_tests_properties( + unit-parallel-label-invalid-3-rank + PROPERTIES + PROCESSORS 3 + RUN_SERIAL TRUE + TIMEOUT 5 +) + +foreach( + failure_case + IN ITEMS + edge-source + edge-target + node-weight +) + if(failure_case STREQUAL "edge-source") + set( + failure_filter + "quotient redistribution rejects a tail-padding edge source collectively" + ) + elseif(failure_case STREQUAL "edge-target") + set( + failure_filter + "quotient redistribution rejects a tail-padding edge target collectively" + ) + else() + set( + failure_filter + "quotient redistribution rejects a tail-padding node weight collectively" + ) + endif() + add_test( + NAME unit-parallel-quotient-invalid-${failure_case}-3-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 3 + ${MPIEXEC_PREFLAGS} + $ + "${failure_filter}" + ${MPIEXEC_POSTFLAGS} + ) + set_tests_properties( + unit-parallel-quotient-invalid-${failure_case}-3-rank + PROPERTIES + PROCESSORS 3 + RUN_SERIAL TRUE + TIMEOUT 5 + ) +endforeach() + +add_test( + NAME unit-parallel-quotient-domain-skew-3-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 3 + ${MPIEXEC_PREFLAGS} + $ + "quotient redistribution rejects an empty-payload coarse-count mismatch collectively" + ${MPIEXEC_POSTFLAGS} +) + +foreach( + receive_case + IN ITEMS + label-request-owner + label-reply-correlation + label-reply-domain + quotient-edge-owner + quotient-edge-target-domain + quotient-edge-sequence + quotient-weight-owner +) + if(receive_case STREQUAL "label-request-owner") + set( + receive_filter + "label request receive validation rejects a valid wrong-owner record collectively" + ) + elseif(receive_case STREQUAL "label-reply-correlation") + set( + receive_filter + "label reply receive validation rejects bad keyed correlation collectively" + ) + elseif(receive_case STREQUAL "label-reply-domain") + set( + receive_filter + "label reply receive validation rejects an out-of-domain coarse ID collectively" + ) + elseif(receive_case STREQUAL "quotient-edge-owner") + set( + receive_filter + "quotient edge receive validation rejects a valid wrong-owner source collectively" + ) + elseif(receive_case STREQUAL "quotient-edge-target-domain") + set( + receive_filter + "quotient edge receive validation rejects an out-of-domain target collectively" + ) + elseif(receive_case STREQUAL "quotient-edge-sequence") + set( + receive_filter + "quotient edge receive validation rejects a sender-sequence gap collectively" + ) + else() + set( + receive_filter + "quotient node-weight receive validation rejects a valid wrong-owner ID collectively" + ) + endif() + add_test( + NAME unit-parallel-receive-${receive_case}-3-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 3 + ${MPIEXEC_PREFLAGS} + $ + "${receive_filter}" + ${MPIEXEC_POSTFLAGS} + ) + set_tests_properties( + unit-parallel-receive-${receive_case}-3-rank + PROPERTIES + PROCESSORS 3 + RUN_SERIAL TRUE + TIMEOUT 5 + ) +endforeach() +set_tests_properties( + unit-parallel-quotient-domain-skew-3-rank + PROPERTIES + PROCESSORS 3 + RUN_SERIAL TRUE + TIMEOUT 5 +) + +add_executable( + parallel_projection_mpi_test + parallel_projection/parallel_projection_mpi_test.cpp +) +target_link_libraries( + parallel_projection_mpi_test + PRIVATE catch_mpi_runner parallel kahip_options kahip_warnings +) +add_test( + NAME unit-pmpi-callback-safety + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/parallel_projection/parallel_projection_mpi_test.cpp" + "-DPROFILE=projection" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_pmpi_callback_safety.cmake" +) +set_tests_properties(unit-pmpi-callback-safety PROPERTIES TIMEOUT 5) + +add_test( + NAME unit-pmpi-callback-safety-ghost-label + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/distributed_consistency/ghost_label_exchange_mpi_test.cpp" + "-DPROFILE=ghost-label" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_pmpi_callback_safety.cmake" +) +set_tests_properties( + unit-pmpi-callback-safety-ghost-label + PROPERTIES TIMEOUT 5 +) + +add_test( + NAME unit-pmpi-callback-safety-ghost-label-failure + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/distributed_consistency/ghost_label_failure_probe.cpp" + "-DPROFILE=ghost-label-failure" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_pmpi_callback_safety.cmake" +) +set_tests_properties( + unit-pmpi-callback-safety-ghost-label-failure + PROPERTIES TIMEOUT 5 +) + +add_executable( + parallel_block_down_mpi_test + parallel_projection/parallel_block_down_mpi_test.cpp +) +target_link_libraries( + parallel_block_down_mpi_test + PRIVATE catch_mpi_runner parallel kahip_options kahip_warnings +) +function(add_parallel_block_down_mpi_test test_name ranks test_filter) + add_test( + NAME "${test_name}" + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" "${ranks}" + ${MPIEXEC_PREFLAGS} + $ + "${test_filter}" + ${MPIEXEC_POSTFLAGS} + ) + set_tests_properties( + "${test_name}" + PROPERTIES PROCESSORS "${ranks}" RUN_SERIAL TRUE TIMEOUT 15 + ) +endfunction() + +add_parallel_block_down_mpi_test( + unit-parallel-block-down-protocol-2-rank 2 + "block-down uses one typed dense and one blocking neighborhood transaction" +) +add_parallel_block_down_mpi_test( + unit-parallel-block-down-datatype-1-rank 1 + "block-down wire datatype has exact semantic extent" +) +foreach(rank RANGE 1 5) + add_parallel_block_down_mpi_test( + "unit-parallel-block-down-matrix-${rank}-rank" "${rank}" + "block-down covers distributed rank-one through rank-five shapes" + ) +endforeach() +add_parallel_block_down_mpi_test( + unit-parallel-block-down-empty-1-rank 1 + "globally empty block-down participates without sentinels" +) +add_parallel_block_down_mpi_test( + unit-parallel-block-down-reuse-2-rank 2 + "block-down reuses a warm topology and refreshes staged blocks" +) +add_parallel_block_down_mpi_test( + unit-parallel-block-down-dense-failure-2-rank 2 + "dense block-down receive failures preserve state and retry" +) +add_parallel_block_down_mpi_test( + unit-parallel-block-down-dense-coverage-3-rank 3 + "dense block-down exact coverage rejects a replaced owner record" +) +foreach(rank IN ITEMS 2 3 4) + add_parallel_block_down_mpi_test( + "unit-parallel-block-down-neighbor-failure-${rank}-rank" "${rank}" + "neighborhood block-down failures preserve state and retry" + ) +endforeach() +add_parallel_block_down_mpi_test( + unit-parallel-block-down-local-domain-2-rank 2 + "block-down rejects a rank-local block equal to k before topology" +) +add_parallel_block_down_mpi_test( + unit-parallel-block-down-skewed-k-2-rank 2 + "block-down rejects rank-skewed k before topology" +) +add_parallel_block_down_mpi_test( + unit-parallel-block-down-zero-k-2-rank 2 + "block-down rejects zero k before topology" +) +add_parallel_block_down_mpi_test( + unit-parallel-block-down-communicator-2-rank 2 + "block-down accepts congruent and rejects similar communicators" +) +add_parallel_block_down_mpi_test( + unit-parallel-block-down-coarse-domain-3-rank 3 + "block-down rejects skewed coarse domains before topology" +) +add_parallel_block_down_mpi_test( + unit-parallel-block-down-ownership-2-rank 2 + "block-down rejects skewed ownership metadata before topology" +) +add_parallel_block_down_mpi_test( + unit-parallel-block-down-asymmetric-2-rank 2 + "block-down rejects asymmetric ghost topology before dense payload" +) +add_test( + NAME unit-parallel-projection-mpi + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 2 + ${MPIEXEC_PREFLAGS} + $ + ${MPIEXEC_POSTFLAGS} +) +set_tests_properties( + unit-parallel-projection-mpi + PROPERTIES + PROCESSORS 2 + RUN_SERIAL TRUE + TIMEOUT 10 +) + +function(add_projection_validation_test test_name mpi_ranks catch_filter) + add_test( + NAME ${test_name} + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" ${mpi_ranks} + ${MPIEXEC_PREFLAGS} + $ + "${catch_filter}" + ${MPIEXEC_POSTFLAGS} + ) + set_tests_properties( + ${test_name} + PROPERTIES + PROCESSORS ${mpi_ranks} + RUN_SERIAL TRUE + TIMEOUT 5 + ) +endfunction() + +add_projection_validation_test( + unit-parallel-vcycle-complete-graph-distribution-2-rank 2 + "complete-graph distribution preserves root vcycle blocks while replicating structure" +) + +foreach(rank RANGE 1 5) + add_projection_validation_test( + "unit-mpi-tools-complete-graph-${rank}-rank" ${rank} + "[complete-graph]" + ) +endforeach() + +add_executable( + mpi_tools_failure_probe + communication/mpi_tools_failure_probe.cpp +) +target_link_libraries( + mpi_tools_failure_probe + PRIVATE parallel kahip_options kahip_warnings +) +add_test( + NAME unit-pmpi-callback-safety-mpi-tools-failure + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/communication/mpi_tools_failure_probe.cpp" + -DPROFILE=mpi-tools-failure + -P "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_pmpi_callback_safety.cmake" +) +set_tests_properties( + unit-pmpi-callback-safety-mpi-tools-failure + PROPERTIES TIMEOUT 5 +) +add_test( + NAME unit-mpi-tools-count-exchange-backend-failure + COMMAND + "${CMAKE_COMMAND}" + "-DPROBE=$" + -DMODE=backend + -DEXPECT_FAILURE=ON + -DMPI_RANKS=2 + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DEXPECTED_MARKER=mpi-tools-count-exchange affected-communicator; internal MPI_Finalize counter is zero" + "-DEXPECTED_DIAGNOSTIC=MPI backend failure: MPI_Alltoall(exchange dense counts)" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_mpi_fail_fast_probe.cmake" +) +set_tests_properties( + unit-mpi-tools-count-exchange-backend-failure + PROPERTIES PROCESSORS 2 RUN_SERIAL TRUE TIMEOUT 12 +) + +add_test( + NAME unit-mpi-tools-unsafe-profile-aborts-before-payload + COMMAND + "${CMAKE_COMMAND}" + "-DPROBE=$" + -DMODE=unsafe-profile + -DEXPECT_FAILURE=ON + -DMPI_RANKS=2 + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DEXPECTED_MARKER=mpi-tools-unsafe-profile affected-communicator; payload all-to-all calls are zero" + "-DEXPECTED_DIAGNOSTIC=ParHIP serial kernel profile failure: reason=block-count-out-of-range" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_mpi_fail_fast_probe.cmake" +) +set_tests_properties( + unit-mpi-tools-unsafe-profile-aborts-before-payload + PROPERTIES PROCESSORS 2 RUN_SERIAL TRUE TIMEOUT 12 +) + +add_test( + NAME unit-mpi-tools-structural-gate-rejects-self-loop + COMMAND + "${CMAKE_COMMAND}" + "-DPROBE=$" + -DMODE=structural-self-loop + -DEXPECT_FAILURE=ON + -DMPI_RANKS=2 + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DEXPECTED_MARKER=mpi-tools-structural-self-loop affected-communicator" + "-DEXPECTED_DIAGNOSTIC=ParHIP serial kernel structural validation failure: expected loop-free reciprocal-undirected weighted adjacency" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_mpi_fail_fast_probe.cmake" +) +set_tests_properties( + unit-mpi-tools-structural-gate-rejects-self-loop + PROPERTIES PROCESSORS 2 RUN_SERIAL TRUE TIMEOUT 12 +) + +foreach(config_mismatch IN ITEMS vcycle initial-algorithm) + add_test( + NAME unit-mpi-tools-${config_mismatch}-mismatch-aborts-before-payload + COMMAND + "${CMAKE_COMMAND}" + "-DPROBE=$" + "-DMODE=config-${config_mismatch}-mismatch" + -DEXPECT_FAILURE=ON + -DMPI_RANKS=2 + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DEXPECTED_MARKER=mpi-tools-config-${config_mismatch}-mismatch affected-communicator; payload all-to-all calls are zero" + "-DEXPECTED_DIAGNOSTIC=ParHIP serial kernel profile failure: reason=collective-configuration-mismatch" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_mpi_fail_fast_probe.cmake" + ) + set_tests_properties( + unit-mpi-tools-${config_mismatch}-mismatch-aborts-before-payload + PROPERTIES PROCESSORS 2 RUN_SERIAL TRUE TIMEOUT 12 + ) +endforeach() + +add_projection_validation_test( + unit-parallel-projection-domain-skew-3-rank + 3 + "projection rejects an empty-payload coarse-count mismatch collectively" +) +add_projection_validation_test( + unit-parallel-projection-zero-3-rank + 3 + "zero-node projection performs two empty dense exchanges" +) +add_projection_validation_test( + unit-parallel-projection-invalid-tail-3-rank + 3 + "projection rejects a tail coarse node before exchanging or mutating" +) +add_projection_validation_test( + unit-parallel-projection-uneven-3-rank + 3 + "projection routes an uneven coarse domain by exact ownership" +) +add_projection_validation_test( + unit-parallel-projection-request-corruption-2-rank + 2 + "projection request corruption fails before replies and preserves labels" +) +add_projection_validation_test( + unit-parallel-projection-reply-corruption-2-rank + 2 + "projection reply corruption fails transactionally" +) +add_projection_validation_test( + unit-parallel-projection-reply-duplicate-2-rank + 2 + "projection rejects duplicate replies without partial label writes" +) + +add_test( + NAME unit-parallel-block-owner-local-conflict-3-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 3 + ${MPIEXEC_PREFLAGS} + $ + "block-down rejects a rank-local conflicting coarse block collectively" + ${MPIEXEC_POSTFLAGS} +) + +add_test( + NAME unit-parallel-block-owner-identical-duplicate-3-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 3 + ${MPIEXEC_PREFLAGS} + $ + "block-down accepts an identical same-sender duplicate" + ${MPIEXEC_POSTFLAGS} +) +set_tests_properties( + unit-parallel-block-owner-identical-duplicate-3-rank + PROPERTIES + PROCESSORS 3 + RUN_SERIAL TRUE + TIMEOUT 10 +) + +add_test( + NAME unit-parallel-block-owner-domain-skew-3-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 3 + ${MPIEXEC_PREFLAGS} + $ + "block-down rejects an empty-payload coarse-count mismatch collectively" + ${MPIEXEC_POSTFLAGS} +) +set_tests_properties( + unit-parallel-block-owner-domain-skew-3-rank + PROPERTIES + PROCESSORS 3 + RUN_SERIAL TRUE + TIMEOUT 5 +) +set_tests_properties( + unit-parallel-block-owner-local-conflict-3-rank + PROPERTIES + PROCESSORS 3 + RUN_SERIAL TRUE + TIMEOUT 5 +) + +foreach( + failure_case + IN ITEMS + invalid-id + cross-rank-conflict + missing +) + if(failure_case STREQUAL "invalid-id") + set( + failure_filter + "block-down rejects a tail-padding coarse ID collectively" + ) + elseif(failure_case STREQUAL "cross-rank-conflict") + set( + failure_filter + "block-down rejects a cross-rank conflicting coarse block collectively" + ) + else() + set( + failure_filter + "block-down rejects a missing coarse block collectively" + ) + endif() + add_test( + NAME unit-parallel-block-owner-${failure_case}-3-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 3 + ${MPIEXEC_PREFLAGS} + $ + "${failure_filter}" + ${MPIEXEC_POSTFLAGS} + ) + set_tests_properties( + unit-parallel-block-owner-${failure_case}-3-rank + PROPERTIES + PROCESSORS 3 + RUN_SERIAL TRUE + TIMEOUT 5 + ) +endforeach() + +add_executable(mpi_adapter_test communication/mpi_adapter_test.cpp) +target_link_libraries( + mpi_adapter_test + PRIVATE + catch_mpi_runner + parallel + parhip_interface + kahip_options + kahip_warnings +) + +foreach(mpi_ranks RANGE 1 5) + add_test( + NAME unit-mpi-adapter-${mpi_ranks}-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" ${mpi_ranks} + ${MPIEXEC_PREFLAGS} + $ + ${MPIEXEC_POSTFLAGS} + ) + set_tests_properties( + unit-mpi-adapter-${mpi_ranks}-rank + PROPERTIES + PROCESSORS ${mpi_ranks} + RUN_SERIAL TRUE + TIMEOUT 10 + ) +endforeach() + +add_executable( + cube_scale_probe_protocol_mpi_test + scale/cube_scale_probe_protocol_mpi_test.cpp +) +target_sources( + cube_scale_probe_protocol_mpi_test + PRIVATE + FILE_SET cube_scale_probe_protocol_headers + TYPE HEADERS + BASE_DIRS + "${CMAKE_CURRENT_SOURCE_DIR}" + "${PROJECT_SOURCE_DIR}/parallel/shared" + "${PROJECT_SOURCE_DIR}/parallel/parallel_src/lib" + "${PARHIP_GENERATED_INCLUDE_DIR}" + FILES + "${CMAKE_CURRENT_SOURCE_DIR}/scale/cube_scale_probe_core.h" + "${CMAKE_CURRENT_SOURCE_DIR}/scale/cube_scale_probe_protocol.h" + "${PROJECT_SOURCE_DIR}/parallel/shared/range_owner.h" + "${PROJECT_SOURCE_DIR}/parallel/parallel_src/lib/communication/mpi_adapter.h" + "${PARHIP_GENERATED_INCLUDE_DIR}/kahip_mpi_capabilities.h" +) +target_link_libraries( + cube_scale_probe_protocol_mpi_test + PRIVATE + catch_mpi_runner + parhip_interface + MPI::MPI_CXX + kahip_options + kahip_warnings +) +foreach(mpi_ranks IN ITEMS 1 3) + add_test( + NAME unit-cube-scale-protocol-${mpi_ranks}-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" ${mpi_ranks} + ${MPIEXEC_PREFLAGS} + $ + ${MPIEXEC_POSTFLAGS} + ) + set_tests_properties( + unit-cube-scale-protocol-${mpi_ranks}-rank + PROPERTIES + PROCESSORS ${mpi_ranks} + RUN_SERIAL TRUE + TIMEOUT 30 + LABELS "unit;mpi;scale;protocol" + ) +endforeach() + +if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "Darwin") +add_executable( + parhip_cube_scale_probe + scale/parhip_cube_scale_probe.cpp +) +set( + KAHIP_SCALE_PROBE_SOURCE_REVISION + "unknown" + CACHE STRING + "Source revision recorded by parhip_cube_scale_probe" +) +if(NOT KAHIP_SCALE_PROBE_SOURCE_REVISION MATCHES "^[0-9A-Za-z._+-]+$") + message( + FATAL_ERROR + "KAHIP_SCALE_PROBE_SOURCE_REVISION must be a simple revision token" + ) +endif() +target_sources( + parhip_cube_scale_probe + PRIVATE + FILE_SET parhip_cube_scale_probe_headers + TYPE HEADERS + BASE_DIRS + "${CMAKE_CURRENT_SOURCE_DIR}" + "${PROJECT_SOURCE_DIR}/parallel/shared" + "${PROJECT_SOURCE_DIR}/parallel/parallel_src/lib" + "${PARHIP_GENERATED_INCLUDE_DIR}" + "${PROJECT_SOURCE_DIR}/lib/version" + FILES + "${CMAKE_CURRENT_SOURCE_DIR}/scale/cube_scale_probe_core.h" + "${CMAKE_CURRENT_SOURCE_DIR}/scale/cube_scale_probe_protocol.h" + "${PROJECT_SOURCE_DIR}/parallel/shared/imbalance.h" + "${PROJECT_SOURCE_DIR}/parallel/shared/range_owner.h" + "${PROJECT_SOURCE_DIR}/parallel/shared/serial_kernel_profile.h" + "${PROJECT_SOURCE_DIR}/parallel/shared/serial_kernel_structure.h" + "${PROJECT_SOURCE_DIR}/parallel/parallel_src/lib/communication/mpi_adapter.h" + "${PROJECT_SOURCE_DIR}/parallel/parallel_src/lib/communication/mpi_tools.h" + "${PROJECT_SOURCE_DIR}/parallel/parallel_src/lib/communication/serial_kernel_profile_observer.h" + "${PARHIP_GENERATED_INCLUDE_DIR}/kahip_mpi_capabilities.h" + "${PROJECT_SOURCE_DIR}/lib/version/version.h" + FILE_SET parhip_cube_scale_probe_tool_headers + TYPE HEADERS + BASE_DIRS "${PROJECT_SOURCE_DIR}/parallel/parallel_src/lib/tools" + FILES + "${PROJECT_SOURCE_DIR}/parallel/parallel_src/lib/tools/macros_assertions.h" +) +target_compile_definitions( + parhip_cube_scale_probe + PRIVATE + KAHIP_SCALE_PROBE_PROJECT_VERSION="${PROJECT_VERSION}" + KAHIP_SCALE_PROBE_SOURCE_REVISION="${KAHIP_SCALE_PROBE_SOURCE_REVISION}" + KAHIP_SCALE_PROBE_COMPILER_ID="${CMAKE_CXX_COMPILER_ID}" + KAHIP_SCALE_PROBE_COMPILER_VERSION="${CMAKE_CXX_COMPILER_VERSION}" + KAHIP_SCALE_PROBE_BUILD_TYPE="$" + KAHIP_SCALE_PROBE_CXX_STANDARD=23 +) +target_compile_features(parhip_cube_scale_probe PRIVATE cxx_std_23) +# The observer scope and public C call must resolve through this one shared +# image. In particular, do not add the `parallel` target here. +target_link_libraries( + parhip_cube_scale_probe + PRIVATE + parhip_interface + kahip_version + MPI::MPI_CXX + kahip_options + kahip_warnings +) + +add_test( + NAME unit-cube-scale-probe-cli-failures + COMMAND + "${CMAKE_COMMAND}" + "-DPROBE=$" + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + -P "${CMAKE_CURRENT_SOURCE_DIR}/scale/verify_cube_scale_probe_cli.cmake" +) +set_tests_properties( + unit-cube-scale-probe-cli-failures + PROPERTIES + PROCESSORS 1 + RUN_SERIAL TRUE + TIMEOUT 30 + LABELS "unit;mpi;scale;cli" +) + +function( + add_cube_scale_probe_test + name + side + ranks + vertices + undirected_edges + directed_edges + maximum_local_nodes + bound + graph_digest + labels + timeout +) + math(EXPR command_timeout "${timeout} - 10") + add_test( + NAME ${name} + COMMAND + "${CMAKE_COMMAND}" + "-DPROBE=$" + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DWORK_DIRECTORY=${CMAKE_CURRENT_BINARY_DIR}/${name}" + "-DSIDE=${side}" + "-DRANKS=${ranks}" + "-DEXPECTED_VERTICES=${vertices}" + "-DEXPECTED_UNDIRECTED_EDGES=${undirected_edges}" + "-DEXPECTED_DIRECTED_EDGES=${directed_edges}" + "-DEXPECTED_MAXIMUM_LOCAL_NODES=${maximum_local_nodes}" + "-DEXPECTED_BOUND=${bound}" + "-DEXPECTED_GRAPH_DIGEST=${graph_digest}" + "-DEXPECTED_PROJECT_VERSION=${PROJECT_VERSION}" + "-DEXPECTED_PLATFORM=${CMAKE_SYSTEM_NAME}" + "-DEXECUTION_TIMEOUT=${command_timeout}" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/scale/verify_cube_scale_probe_result.cmake" + ) + set_tests_properties( + ${name} + PROPERTIES + PROCESSORS ${ranks} + RUN_SERIAL TRUE + TIMEOUT ${timeout} + LABELS "${labels}" + ) +endfunction() + +add_cube_scale_probe_test( + integration-cube-scale-probe-side4-2-rank + 4 2 64 144 288 32 32 + "f12b9a02fe2a75b7,e6a832aefcaa2a99,dd15134b3a2a0e43,30789a9a367d34f9" + "integration;mpi;scale" + 120 +) +add_cube_scale_probe_test( + integration-cube-scale-probe-side10-5-rank + 10 5 1000 2700 5400 200 206 + "73e09509b201c17a,402fd2d71ea65a5b,d002e5024a1072db,bcc02b4ee29b27bd" + "integration;mpi;scale" + 180 +) + +add_cube_scale_probe_test( + large-cube-scale-probe-side600-2304-rank + 600 2304 216000000 646920000 1293840000 93750 96562 + "" + "large;scale;mpi" + 7200 +) +add_cube_scale_probe_test( + large-cube-scale-probe-side755-4608-rank + 755 4608 430368875 1289396550 2578793100 93397 96198 + "" + "large;scale;mpi" + 7200 +) +add_cube_scale_probe_test( + large-cube-scale-probe-side900-7776-rank + 900 7776 729000000 2184570000 4369140000 93750 96562 + "" + "large;scale;mpi" + 7200 +) +add_cube_scale_probe_test( + large-cube-scale-probe-side1008-10944-rank + 1008 10944 1024192512 3069529344 6139058688 93585 96392 + "" + "large;scale;mpi" + 7200 +) +endif() + +add_test( + NAME unit-mpi-semantic-exit-policy + COMMAND + "${CMAKE_COMMAND}" + "-DCOMMUNICATION_ROOT=${CMAKE_CURRENT_SOURCE_DIR}/../lib/communication" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_mpi_semantic_exit_policy.cmake" +) +set_tests_properties(unit-mpi-semantic-exit-policy PROPERTIES TIMEOUT 5) + +add_executable( + mpi_failure_policy_probe + communication/mpi_failure_policy_probe.cpp +) +target_link_libraries( + mpi_failure_policy_probe + PRIVATE parallel kahip_options kahip_warnings +) + +add_test( + NAME unit-mpi-failure-policy-pre-init-error + COMMAND + "${CMAKE_COMMAND}" + "-DPROBE=$" + "-DMODE=pre-init-error" + "-DEXPECT_FAILURE=FALSE" + "-DEXPECTED_MARKER=pre-init mpi_error remained MPI-free" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_mpi_fail_fast_probe.cmake" +) +set_tests_properties( + unit-mpi-failure-policy-pre-init-error + PROPERTIES RUN_SERIAL TRUE TIMEOUT 10 +) + +function( + add_mpi_failure_policy_death_test + test_suffix + mode + expected_diagnostic + expected_detail + expected_injection + expected_marker +) + set(mpi_ranks 2) + if(ARGC GREATER 6) + set(mpi_ranks "${ARGV6}") + endif() + add_test( + NAME unit-mpi-failure-policy-${test_suffix} + COMMAND + "${CMAKE_COMMAND}" + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DMPI_RANKS=${mpi_ranks}" + "-DPROBE=$" + "-DMODE=${mode}" + "-DEXPECT_FAILURE=TRUE" + "-DEXPECTED_DIAGNOSTIC=${expected_diagnostic}" + "-DEXPECTED_DETAIL=${expected_detail}" + "-DEXPECTED_INJECTION=${expected_injection}" + "-DEXPECTED_MARKER=${expected_marker}" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_mpi_fail_fast_probe.cmake" + ) + set_tests_properties( + unit-mpi-failure-policy-${test_suffix} + PROPERTIES PROCESSORS ${mpi_ranks} RUN_SERIAL TRUE TIMEOUT 10 + ) +endfunction() + +add_mpi_failure_policy_death_test( + semantic-factory-resource-failure + semantic-factory-resource + "MPI semantic error construction:" + "std::bad_alloc" + "injected semantic factory bad_alloc" + "affected=semantic error-string-attempts=0 cleanup-attempts=0" +) +add_mpi_failure_policy_death_test( + error-string-secondary-failure + error-string-secondary + "MPI backend failure: backend formatter failure" + "original raw code 17291, MPI_Error_string secondary raw code 17292" + "injected MPI_Error_string failure original=17291 secondary=17292" + "affected=backend error-string-attempts=1 cleanup-attempts=0" +) +add_mpi_failure_policy_death_test( + null-communicator-failure + null-communicator + "MPI adapter programming failure: communicator duplication requires a live intracommunicator" + "" + "injected null communicator construction" + "affected=world error-string-attempts=0 cleanup-attempts=0 capacity-allreduce-attempts=0 dense-count-exchange-attempts=0 dense-payload-attempts=0 dense-datatype-attempts=0 graph-semantic-validation-attempts=0 graph-create-attempts=0 neighbor-count-exchange-attempts=0 neighbor-payload-attempts=0 neighbor-datatype-attempts=0 neighbor-phase-round-attempts=0 neighbor-capacity-after-injection-attempts=0" +) +add_mpi_failure_policy_death_test( + intercommunicator-failure + intercommunicator + "MPI adapter programming failure: communicator duplication requires a live intracommunicator" + "" + "injected intercommunicator construction" + "affected=communicator-guard error-string-attempts=0 cleanup-attempts=0 capacity-allreduce-attempts=0 dense-count-exchange-attempts=0 dense-payload-attempts=0 dense-datatype-attempts=0 graph-semantic-validation-attempts=0 graph-create-attempts=0 neighbor-count-exchange-attempts=0 neighbor-payload-attempts=0 neighbor-datatype-attempts=0 neighbor-phase-round-attempts=0 neighbor-capacity-after-injection-attempts=0" +) +add_mpi_failure_policy_death_test( + null-distributed-graph-failure + null-distributed-graph + "MPI adapter programming failure: distributed graph construction requires a live intracommunicator" + "" + "injected null distributed graph construction" + "affected=world error-string-attempts=0 cleanup-attempts=0 capacity-allreduce-attempts=0 dense-count-exchange-attempts=0 dense-payload-attempts=0 dense-datatype-attempts=0 graph-semantic-validation-attempts=0 graph-create-attempts=0 neighbor-count-exchange-attempts=0 neighbor-payload-attempts=0 neighbor-datatype-attempts=0 neighbor-phase-round-attempts=0 neighbor-capacity-after-injection-attempts=0" +) +add_mpi_failure_policy_death_test( + wrong-topology-failure + wrong-topology + "MPI adapter programming failure: topology requires an MPI topology communicator" + "" + "captured wrong-topology internal duplicate" + "affected=topology error-string-attempts=0 cleanup-attempts=0" +) +add_mpi_failure_policy_death_test( + capacity-resolver-failure + capacity-resolver + "MPI adapter capacity failure:" + "capacity resolver probe: cumulative element offset exceeds local size_t capacity" + "injected rank-zero fatal capacity issue" + "affected=capacity error-string-attempts=0 cleanup-attempts=0 capacity-allreduce-attempts=1" +) +add_mpi_failure_policy_death_test( + dense-receive-offset-capacity-failure + dense-receive-offset-capacity + "MPI adapter capacity failure:" + "all_to_all_v: cumulative element offset exceeds local size_t capacity" + "injected rank-zero dense receive offset capacity" + "affected=dense-operation error-string-attempts=0 cleanup-attempts=0 capacity-allreduce-attempts=1 dense-count-exchange-attempts=1 dense-payload-attempts=0 dense-datatype-attempts=0" +) + +add_mpi_failure_policy_death_test( + dense-receive-byte-capacity-failure + dense-receive-byte-capacity + "MPI adapter capacity failure:" + "all_to_all_v: element storage byte size exceeds local size_t capacity" + "injected rank-zero dense receive byte capacity" + "affected=dense-operation error-string-attempts=0 cleanup-attempts=0 capacity-allreduce-attempts=1 dense-count-exchange-attempts=1 dense-payload-attempts=0 dense-datatype-attempts=0" +) + +foreach(mpi_ranks RANGE 1 5) + add_mpi_failure_policy_death_test( + distributed-graph-degree-capacity-failure-${mpi_ranks}-rank + distributed-graph-degree-capacity + "MPI adapter capacity failure:" + "distributed graph construction: distributed graph outdegree exceeds MPI int capacity" + "injected rank-zero distributed graph degree capacity" + "affected=world error-string-attempts=0 cleanup-attempts=0 capacity-allreduce-attempts=1 dense-count-exchange-attempts=0 dense-payload-attempts=0 dense-datatype-attempts=0 graph-semantic-validation-attempts=1 graph-create-attempts=0" + ${mpi_ranks} + ) +endforeach() + +add_mpi_failure_policy_death_test( + neighbor-receive-offset-capacity-failure + neighbor-receive-offset-capacity + "MPI adapter capacity failure:" + "neighbor_all_to_all_v: cumulative element offset exceeds local size_t capacity" + "injected rank-zero neighbor receive offset capacity" + "affected=neighbor-graph error-string-attempts=0 cleanup-attempts=0 capacity-allreduce-attempts=1 dense-count-exchange-attempts=0 dense-payload-attempts=0 dense-datatype-attempts=0 graph-semantic-validation-attempts=0 graph-create-attempts=0 neighbor-count-exchange-attempts=1 neighbor-payload-attempts=0 neighbor-datatype-attempts=0 neighbor-phase-round-attempts=0 neighbor-capacity-after-injection-attempts=1" +) + +add_mpi_failure_policy_death_test( + neighbor-receive-byte-capacity-failure + neighbor-receive-byte-capacity + "MPI adapter capacity failure:" + "neighbor_all_to_all_v: element storage byte size exceeds local size_t capacity" + "injected rank-zero neighbor receive byte capacity" + "affected=neighbor-graph error-string-attempts=0 cleanup-attempts=0 capacity-allreduce-attempts=1 dense-count-exchange-attempts=0 dense-payload-attempts=0 dense-datatype-attempts=0 graph-semantic-validation-attempts=0 graph-create-attempts=0 neighbor-count-exchange-attempts=1 neighbor-payload-attempts=0 neighbor-datatype-attempts=0 neighbor-phase-round-attempts=0 neighbor-capacity-after-injection-attempts=1" +) + +add_mpi_failure_policy_death_test( + neighbor-bounded-round-arithmetic-failure + neighbor-bounded-round-arithmetic + "MPI adapter capacity failure:" + "neighbor_all_to_all_v bounded MPI-3 plan: bounded MPI-3 chunk arithmetic exceeds local size_t capacity" + "injected rank-zero bounded neighbor round arithmetic capacity" + "affected=neighbor-graph error-string-attempts=0 cleanup-attempts=0 capacity-allreduce-attempts=2 dense-count-exchange-attempts=0 dense-payload-attempts=0 dense-datatype-attempts=0 graph-semantic-validation-attempts=0 graph-create-attempts=0 neighbor-count-exchange-attempts=1 neighbor-payload-attempts=0 neighbor-datatype-attempts=0 neighbor-phase-round-attempts=2 neighbor-capacity-after-injection-attempts=1" +) + +add_executable( + mpi_async_capacity_failure_probe + communication/mpi_async_capacity_failure_probe.cpp +) +target_link_libraries( + mpi_async_capacity_failure_probe + PRIVATE parallel kahip_options kahip_warnings +) + +function(add_mpi_async_capacity_death_test mode expected_detail expected_injection) + add_test( + NAME unit-mpi-async-capacity-${mode}-failure + COMMAND + "${CMAKE_COMMAND}" + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DMPI_RANKS=2" + "-DPROBE=$" + "-DMODE=${mode}" + "-DEXPECT_FAILURE=TRUE" + "-DEXPECTED_DIAGNOSTIC=MPI adapter capacity failure:" + "-DEXPECTED_DETAIL=direct neighborhood exchange: ${expected_detail}" + "-DEXPECTED_INJECTION=${expected_injection}" + "-DEXPECTED_MARKER=affected=async-operation error-string-attempts=0 cleanup-attempts=0 capacity-bor-attempts=1 backend-band-attempts=1 count-exchange-attempts=1 payload-allocation-attempts=0 datatype-attempts=0 immediate-init-attempts=0 persistent-init-attempts=0 payload-collective-attempts=0 operation-duplicate-attempts=1" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_mpi_fail_fast_probe.cmake" + ) + set_tests_properties( + unit-mpi-async-capacity-${mode}-failure + PROPERTIES PROCESSORS 2 RUN_SERIAL TRUE TIMEOUT 10 + ) +endfunction() + +add_mpi_async_capacity_death_test( + one-shot-receive-offset + "cumulative element offset exceeds local size_t capacity" + "injected rank-zero async receive offset capacity" +) +add_mpi_async_capacity_death_test( + one-shot-receive-byte + "element storage byte size exceeds local size_t capacity" + "injected rank-zero async receive byte capacity" +) +add_mpi_async_capacity_death_test( + fixed-send-offset + "cumulative element offset exceeds local size_t capacity" + "armed rank-zero async fixed-send offset capacity" +) +add_mpi_async_capacity_death_test( + fixed-send-byte + "element storage byte size exceeds local size_t capacity" + "armed rank-zero async fixed-send byte capacity" +) +add_executable( + parallel_graph_consistency_mpi_test + distributed_consistency/parallel_graph_consistency_mpi_test.cpp +) +target_link_libraries( + parallel_graph_consistency_mpi_test + PRIVATE + catch_mpi_runner + parallel + kahip_options + kahip_warnings +) + +foreach(mpi_ranks RANGE 1 5) + add_test( + NAME unit-parallel-graph-consistency-${mpi_ranks}-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" ${mpi_ranks} + ${MPIEXEC_PREFLAGS} + $ + ${MPIEXEC_POSTFLAGS} + ) + set_tests_properties( + unit-parallel-graph-consistency-${mpi_ranks}-rank + PROPERTIES + PROCESSORS ${mpi_ranks} + RUN_SERIAL TRUE + TIMEOUT 20 + ) +endforeach() + +add_executable( + ghost_label_exchange_mpi_test + distributed_consistency/ghost_label_exchange_mpi_test.cpp +) +target_link_libraries( + ghost_label_exchange_mpi_test + PRIVATE + catch_mpi_runner + parallel + kahip_options + kahip_warnings +) + +foreach(mpi_ranks RANGE 1 5) + add_test( + NAME unit-ghost-label-exchange-${mpi_ranks}-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" ${mpi_ranks} + ${MPIEXEC_PREFLAGS} + $ + ${MPIEXEC_POSTFLAGS} + ) + set_tests_properties( + unit-ghost-label-exchange-${mpi_ranks}-rank + PROPERTIES + PROCESSORS ${mpi_ranks} + RUN_SERIAL TRUE + TIMEOUT 25 + ) +endforeach() + +add_executable( + ghost_label_failure_probe + distributed_consistency/ghost_label_failure_probe.cpp +) +target_link_libraries( + ghost_label_failure_probe + PRIVATE parallel kahip_options kahip_warnings +) + +function( + add_ghost_label_failure_test + mode + expected_diagnostic + expected_abort_marker +) + add_test( + NAME unit-ghost-label-${mode}-failure + COMMAND + "${CMAKE_COMMAND}" + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DPROBE=$" + "-DMODE=${mode}" + "-DEXPECTED_DIAGNOSTIC=${expected_diagnostic}" + "-DEXPECTED_ABORT_MARKER=${expected_abort_marker}" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/distributed_consistency/verify_ghost_label_failure.cmake" + ) + set_tests_properties( + unit-ghost-label-${mode}-failure + PROPERTIES PROCESSORS 2 RUN_SERIAL TRUE TIMEOUT 12 + ) +endfunction() + +add_ghost_label_failure_test( + active-incremental-then-global + "MPI adapter programming failure: global ghost label exchange requires no active incremental exchange" + "observed active-incremental/global MPI_Abort before blocking payload" +) +add_ghost_label_failure_test( + skewed-incremental-protocol + "MPI adapter programming failure: ghost label incremental protocol diverged across ranks" + "observed skewed incremental-protocol MPI_Abort before first payload" +) +add_ghost_label_failure_test( + corrupted-incremental-completion + "MPI adapter programming failure: incremental ghost label receive validation failed after payload completion" + "observed corrupted incremental-completion terminal MPI_Abort" +) + +add_executable( + population_size_broadcast_mpi_test + communication/population_size_broadcast_mpi_test.cpp +) +target_sources( + population_size_broadcast_mpi_test + PRIVATE + FILE_SET shared_population_size_headers + TYPE HEADERS + BASE_DIRS "${PROJECT_SOURCE_DIR}/lib" + FILES + "${PROJECT_SOURCE_DIR}/lib/parallel_mh/population_size_broadcast.h" +) +target_link_libraries( + population_size_broadcast_mpi_test + PRIVATE + catch_mpi_runner + MPI::MPI_CXX + kahip_fatal_diagnostics + kahip_options + kahip_warnings +) +add_test( + NAME unit-pmpi-callback-safety-population-size + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/communication/population_size_broadcast_mpi_test.cpp" + "-DPROFILE=population-size" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_pmpi_callback_safety.cmake" +) +set_tests_properties( + unit-pmpi-callback-safety-population-size + PROPERTIES TIMEOUT 5 +) +foreach(mpi_ranks RANGE 1 5) + add_test( + NAME unit-population-size-broadcast-${mpi_ranks}-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" ${mpi_ranks} + ${MPIEXEC_PREFLAGS} + $ + ${MPIEXEC_POSTFLAGS} + ) + set_tests_properties( + unit-population-size-broadcast-${mpi_ranks}-rank + PROPERTIES + PROCESSORS ${mpi_ranks} + RUN_SERIAL TRUE + TIMEOUT 10 + ) +endforeach() + +add_executable( + population_size_broadcast_failure_probe + communication/population_size_broadcast_failure_probe.cpp +) +target_sources( + population_size_broadcast_failure_probe + PRIVATE + FILE_SET shared_population_size_headers + TYPE HEADERS + BASE_DIRS "${PROJECT_SOURCE_DIR}/lib" + FILES + "${PROJECT_SOURCE_DIR}/lib/parallel_mh/population_size_broadcast.h" +) +target_link_libraries( + population_size_broadcast_failure_probe + PRIVATE + MPI::MPI_CXX + kahip_fatal_diagnostics + kahip_options + kahip_warnings +) +add_test( + NAME unit-pmpi-callback-safety-population-size-failure + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/communication/population_size_broadcast_failure_probe.cpp" + "-DPROFILE=population-size-failure" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_pmpi_callback_safety.cmake" +) +set_tests_properties( + unit-pmpi-callback-safety-population-size-failure + PROPERTIES TIMEOUT 5 +) +add_test( + NAME unit-population-size-broadcast-failure + COMMAND + "${CMAKE_COMMAND}" + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DPROBE=$" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_population_size_broadcast_failure.cmake" +) +set_tests_properties( + unit-population-size-broadcast-failure + PROPERTIES PROCESSORS 2 RUN_SERIAL TRUE TIMEOUT 12 +) + +add_executable( + evolutionary_collectives_mpi_test + communication/evolutionary_collectives_mpi_test.cpp +) +target_sources( + evolutionary_collectives_mpi_test + PRIVATE + FILE_SET shared_evolutionary_headers + TYPE HEADERS + BASE_DIRS "${PROJECT_SOURCE_DIR}/lib" + FILES + "${PROJECT_SOURCE_DIR}/lib/parallel_mh/evolutionary_collectives.h" +) +target_link_libraries( + evolutionary_collectives_mpi_test + PRIVATE + catch_mpi_runner + MPI::MPI_CXX + kahip_fatal_diagnostics + kahip_options + kahip_warnings +) +add_test( + NAME unit-pmpi-callback-safety-evolutionary + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/communication/evolutionary_collectives_mpi_test.cpp" + "-DPROFILE=evolutionary" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_pmpi_callback_safety.cmake" +) +set_tests_properties( + unit-pmpi-callback-safety-evolutionary + PROPERTIES TIMEOUT 5 +) +foreach(mpi_ranks RANGE 1 5) + add_test( + NAME unit-evolutionary-collectives-${mpi_ranks}-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" ${mpi_ranks} + ${MPIEXEC_PREFLAGS} + $ + ${MPIEXEC_POSTFLAGS} + ) + set_tests_properties( + unit-evolutionary-collectives-${mpi_ranks}-rank + PROPERTIES PROCESSORS ${mpi_ranks} RUN_SERIAL TRUE TIMEOUT 12 + ) +endforeach() + +add_executable( + evolutionary_collectives_failure_probe + communication/evolutionary_collectives_failure_probe.cpp +) +target_sources( + evolutionary_collectives_failure_probe + PRIVATE + FILE_SET shared_evolutionary_headers + TYPE HEADERS + BASE_DIRS "${PROJECT_SOURCE_DIR}/lib" + FILES + "${PROJECT_SOURCE_DIR}/lib/parallel_mh/evolutionary_collectives.h" +) +target_link_libraries( + evolutionary_collectives_failure_probe + PRIVATE + MPI::MPI_CXX + kahip_fatal_diagnostics + kahip_options + kahip_warnings +) +add_test( + NAME unit-pmpi-callback-safety-evolutionary-failure + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/communication/evolutionary_collectives_failure_probe.cpp" + "-DPROFILE=evolutionary-failure" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_pmpi_callback_safety.cmake" +) +set_tests_properties( + unit-pmpi-callback-safety-evolutionary-failure + PROPERTIES TIMEOUT 5 +) +foreach( + failure_mode + IN ITEMS + permutation + rank + communicator-size + feasibility + objective + weight + root + signature-minimum + signature-maximum + signature-validity + payload + count-mismatch + null-payload +) + if(failure_mode STREQUAL "permutation") + set( + expected_diagnostic + "MPI backend failure: MPI_Bcast(evolutionary permutation)" + ) + elseif(failure_mode STREQUAL "rank") + set( + expected_diagnostic + "MPI backend failure: MPI_Comm_rank(evolutionary best partition)" + ) + elseif(failure_mode STREQUAL "communicator-size") + set( + expected_diagnostic + "MPI backend failure: MPI_Comm_size(evolutionary partition broadcast)" + ) + elseif(failure_mode STREQUAL "feasibility") + set( + expected_diagnostic + "MPI backend failure: MPI_Allreduce(evolutionary feasibility)" + ) + elseif(failure_mode STREQUAL "objective") + set( + expected_diagnostic + "MPI backend failure: MPI_Allreduce(evolutionary objective)" + ) + elseif(failure_mode STREQUAL "weight") + set( + expected_diagnostic + "MPI backend failure: MPI_Allreduce(evolutionary maximum block weight)" + ) + elseif(failure_mode STREQUAL "root") + set( + expected_diagnostic + "MPI backend failure: MPI_Allreduce(evolutionary broadcaster rank)" + ) + elseif(failure_mode STREQUAL "signature-minimum") + set( + expected_diagnostic + "MPI backend failure: MPI_Allreduce(evolutionary partition signature minimum)" + ) + elseif(failure_mode STREQUAL "signature-maximum") + set( + expected_diagnostic + "MPI backend failure: MPI_Allreduce(evolutionary partition signature maximum)" + ) + elseif(failure_mode STREQUAL "signature-validity") + set( + expected_diagnostic + "MPI backend failure: MPI_Allreduce(evolutionary partition signature validity)" + ) + elseif(failure_mode STREQUAL "payload") + if(KAHIP_HAVE_MPI_BCAST_C) + set( + expected_diagnostic + "MPI backend failure: MPI_Bcast_c(evolutionary best partition)" + ) + else() + set( + expected_diagnostic + "MPI backend failure: MPI_Bcast(evolutionary best partition MPI-3 round)" + ) + endif() + elseif(failure_mode STREQUAL "count-mismatch") + set( + expected_diagnostic + "partition broadcast arguments differ across communicator" + ) + else() + set( + expected_diagnostic + "partition broadcast arguments are invalid" + ) + endif() + add_test( + NAME unit-evolutionary-collectives-${failure_mode}-failure + COMMAND + "${CMAKE_COMMAND}" + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DPROBE=$" + "-DMODE=${failure_mode}" + "-DEXPECTED_DIAGNOSTIC=${expected_diagnostic}" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_evolutionary_collectives_failure.cmake" + ) + set_tests_properties( + unit-evolutionary-collectives-${failure_mode}-failure + PROPERTIES PROCESSORS 2 RUN_SERIAL TRUE TIMEOUT 12 + ) +endforeach() + +add_executable( + parallel_graph_lifecycle_probe + distributed_consistency/parallel_graph_lifecycle_probe.cpp +) +target_link_libraries( + parallel_graph_lifecycle_probe + PRIVATE parallel kahip_options kahip_warnings +) +foreach( + graph_lifecycle + IN ITEMS + no-plan + cached-plan + active-destructor + active-reset + cnode-size-mismatch +) + add_test( + NAME unit-parallel-graph-lifecycle-${graph_lifecycle} + COMMAND + "${CMAKE_COMMAND}" + "-DPROBE=$" + "-DMODE=${graph_lifecycle}" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/distributed_consistency/verify_parallel_graph_lifecycle.cmake" + ) + set_tests_properties( + unit-parallel-graph-lifecycle-${graph_lifecycle} + PROPERTIES RUN_SERIAL TRUE TIMEOUT 10 + ) +endforeach() + +add_executable( + distributed_consistency_mpi_test + distributed_consistency/distributed_consistency_mpi_test.cpp +) +target_link_libraries( + distributed_consistency_mpi_test + PRIVATE + catch_mpi_runner + parallel + kahip_options + kahip_warnings +) + +foreach(mpi_ranks RANGE 1 5) + add_test( + NAME unit-distributed-consistency-${mpi_ranks}-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" ${mpi_ranks} + ${MPIEXEC_PREFLAGS} + $ + ${MPIEXEC_POSTFLAGS} + ) + set_tests_properties( + unit-distributed-consistency-${mpi_ranks}-rank + PROPERTIES + PROCESSORS ${mpi_ranks} + RUN_SERIAL TRUE + TIMEOUT 25 + ) +endforeach() + +add_executable( + mpi_async_neighbors_test + communication/mpi_async_neighbors_test.cpp +) +target_link_libraries( + mpi_async_neighbors_test + PRIVATE + catch_mpi_runner + parallel + kahip_options + kahip_warnings +) + +foreach(mpi_ranks RANGE 1 5) + add_test( + NAME unit-mpi-async-neighbors-${mpi_ranks}-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" ${mpi_ranks} + ${MPIEXEC_PREFLAGS} + $ + ${MPIEXEC_POSTFLAGS} + ) + set_tests_properties( + unit-mpi-async-neighbors-${mpi_ranks}-rank + PROPERTIES + PROCESSORS ${mpi_ranks} + RUN_SERIAL TRUE + TIMEOUT 15 + ) +endforeach() + +add_executable( + mpi_async_neighbors_failure_probe + communication/mpi_async_neighbors_failure_probe.cpp +) +target_link_libraries( + mpi_async_neighbors_failure_probe + PRIVATE parallel kahip_options kahip_warnings +) + +function( + add_mpi_async_neighbors_failure_test + mode + abort_kind + expected_diagnostic + expect_injected_mpi_error +) + add_test( + NAME unit-mpi-async-neighbors-${mode}-failure + COMMAND + "${CMAKE_COMMAND}" + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DPROBE=$" + "-DMODE=${mode}" + "-DABORT_KIND=${abort_kind}" + "-DEXPECTED_DIAGNOSTIC=${expected_diagnostic}" + "-DEXPECT_INJECTED_MPI_ERROR=${expect_injected_mpi_error}" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_mpi_async_neighbors_failure.cmake" + ) + set_tests_properties( + unit-mpi-async-neighbors-${mode}-failure + PROPERTIES + RUN_SERIAL TRUE + TIMEOUT 12 + ) +endfunction() + +add_mpi_async_neighbors_failure_test( + immediate-init + mpi + "MPI backend failure: MPI_Ineighbor_alltoallv" + TRUE +) +add_mpi_async_neighbors_failure_test( + test + mpi + "MPI backend failure: MPI_Test(neighborhood exchange)" + TRUE +) +add_mpi_async_neighbors_failure_test( + wait + mpi + "MPI backend failure: MPI_Wait(neighborhood exchange)" + TRUE +) +add_mpi_async_neighbors_failure_test( + destructor-wait + mpi + "MPI backend failure: MPI_Wait(active neighborhood destruction)" + TRUE +) +add_mpi_async_neighbors_failure_test( + active-restart + mpi + "MPI adapter programming failure: neighbor context start requires an inactive generation" + FALSE +) +add_mpi_async_neighbors_failure_test( + inactive-test + mpi + "MPI adapter programming failure: neighbor context test requires an active generation" + FALSE +) +add_mpi_async_neighbors_failure_test( + bounded-inactive-test + mpi + "MPI adapter programming failure: neighbor context test requires an active generation" + FALSE +) +add_mpi_async_neighbors_failure_test( + bounded-inactive-wait + mpi + "MPI adapter programming failure: neighbor context wait requires an active generation" + FALSE +) +add_mpi_async_neighbors_failure_test( + bounded-later-init + mpi + "MPI backend failure: MPI_Ineighbor_alltoallv(MPI-3 bounded neighborhood round)" + TRUE +) +add_mpi_async_neighbors_failure_test( + bounded-later-test + mpi + "MPI backend failure: MPI_Test(MPI-3 bounded neighborhood round)" + TRUE +) +add_mpi_async_neighbors_failure_test( + bounded-later-wait + mpi + "MPI backend failure: MPI_Wait(MPI-3 bounded neighborhood round)" + TRUE +) +add_mpi_async_neighbors_failure_test( + send-while-active + mpi + "MPI adapter programming failure: neighbor context send mutation requires an inactive generation" + FALSE +) +add_mpi_async_neighbors_failure_test( + receive-while-active + mpi + "MPI adapter programming failure: neighbor context receive access requires a completed generation" + FALSE +) + +if( + KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT + OR KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT_C +) + add_mpi_async_neighbors_failure_test( + persistent-init + mpi + "MPI backend failure: MPI_Neighbor_alltoallv_init" + TRUE + ) + add_mpi_async_neighbors_failure_test( + persistent-start + mpi + "MPI backend failure: MPI_Start(persistent neighborhood exchange)" + TRUE + ) + add_mpi_async_neighbors_failure_test( + persistent-test + mpi + "MPI backend failure: MPI_Test(neighborhood exchange)" + TRUE + ) + add_mpi_async_neighbors_failure_test( + persistent-wait + mpi + "MPI backend failure: MPI_Wait(neighborhood exchange)" + TRUE + ) + add_mpi_async_neighbors_failure_test( + request-free + mpi + "MPI backend failure: MPI_Request_free(persistent neighborhood exchange)" + TRUE + ) +endif() + +foreach( + post_finalize_mode + IN ITEMS + post-finalize-active-request + post-finalize-immediate-context +) + add_mpi_async_neighbors_failure_test( + ${post_finalize_mode} + raw + "MPI adapter ownership outlived the active MPI runtime: neighborhood operation destruction" + FALSE + ) +endforeach() +add_mpi_async_neighbors_failure_test( + post-finalize-complete-request + raw + "MPI adapter ownership outlived the active MPI runtime: one-shot neighborhood test" + FALSE +) +add_mpi_async_neighbors_failure_test( + post-finalize-complete-wait + raw + "MPI adapter ownership outlived the active MPI runtime: one-shot neighborhood wait" + FALSE +) +if( + KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT + OR KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT_C +) + add_mpi_async_neighbors_failure_test( + post-finalize-persistent-context + raw + "MPI adapter ownership outlived the active MPI runtime: neighborhood operation destruction" + FALSE + ) +endif() +add_mpi_async_neighbors_failure_test( + initialized-query + raw + "MPI lifecycle query failure: MPI_Initialized returned raw error 17295" + FALSE +) +add_mpi_async_neighbors_failure_test( + finalized-query + raw + "MPI lifecycle query failure: MPI_Finalized returned raw error 17295" + FALSE +) + +add_executable( + mpi_neighborhood_failure_probe + communication/mpi_neighborhood_failure_probe.cpp +) +target_link_libraries( + mpi_neighborhood_failure_probe + PRIVATE parallel kahip_options kahip_warnings +) + +foreach(neighborhood_failure IN ITEMS create query free) + if(neighborhood_failure STREQUAL "create") + set(neighborhood_failure_context "MPI_Dist_graph_create") + set(neighborhood_failure_affected "internal") + elseif(neighborhood_failure STREQUAL "query") + set(neighborhood_failure_context "MPI_Dist_graph_neighbors_count") + set(neighborhood_failure_affected "graph") + else() + set(neighborhood_failure_context "MPI_Comm_free\\(distributed graph\\)") + set(neighborhood_failure_affected "world") + endif() + add_test( + NAME unit-mpi-neighborhood-${neighborhood_failure}-failure + COMMAND + "${CMAKE_COMMAND}" + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DPROBE=$" + "-DMODE=${neighborhood_failure}" + "-DEXPECTED_CONTEXT=${neighborhood_failure_context}" + "-DEXPECTED_AFFECTED=${neighborhood_failure_affected}" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_mpi_neighborhood_failure.cmake" + ) + set_tests_properties( + unit-mpi-neighborhood-${neighborhood_failure}-failure + PROPERTIES + RUN_SERIAL TRUE + TIMEOUT 10 + ) +endforeach() + +add_executable( + mpi_lifecycle_failure_probe + communication/mpi_lifecycle_failure_probe.cpp +) +target_link_libraries( + mpi_lifecycle_failure_probe + PRIVATE parallel kahip_options kahip_warnings +) + +foreach(lifecycle_query IN ITEMS Initialized Finalized) + string(TOLOWER "${lifecycle_query}" lifecycle_mode) + add_test( + NAME unit-mpi-lifecycle-${lifecycle_mode}-query-failure + COMMAND + "${CMAKE_COMMAND}" + "-DPROBE=$" + "-DMODE=${lifecycle_mode}" + "-DEXPECTED_QUERY=MPI_${lifecycle_query}" + "-DEXPECTED_ERROR_CODE=17293" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_mpi_lifecycle_failure.cmake" + ) + set_tests_properties( + unit-mpi-lifecycle-${lifecycle_mode}-query-failure + PROPERTIES + RUN_SERIAL TRUE + TIMEOUT 10 + ) +endforeach() + +foreach(lifecycle_state IN ITEMS before-initialization post-finalization) + add_test( + NAME unit-mpi-lifecycle-${lifecycle_state}-inactive + COMMAND + $ + "${lifecycle_state}" + ) + set_tests_properties( + unit-mpi-lifecycle-${lifecycle_state}-inactive + PROPERTIES + RUN_SERIAL TRUE + TIMEOUT 5 + ) +endforeach() + +add_executable( + mpi_fixed_broadcast_mpi_test + communication/mpi_fixed_broadcast_mpi_test.cpp +) +target_link_libraries( + mpi_fixed_broadcast_mpi_test + PRIVATE + catch_mpi_runner + parallel + kahip_options + kahip_warnings +) +add_test( + NAME unit-pmpi-callback-safety-fixed-broadcast + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/communication/mpi_fixed_broadcast_mpi_test.cpp" + "-DPROFILE=fixed-broadcast" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_pmpi_callback_safety.cmake" +) +set_tests_properties( + unit-pmpi-callback-safety-fixed-broadcast + PROPERTIES TIMEOUT 5 +) +foreach(mpi_ranks RANGE 1 5) + add_test( + NAME unit-mpi-fixed-broadcast-${mpi_ranks}-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" ${mpi_ranks} + ${MPIEXEC_PREFLAGS} + $ + ${MPIEXEC_POSTFLAGS} + ) + set_tests_properties( + unit-mpi-fixed-broadcast-${mpi_ranks}-rank + PROPERTIES PROCESSORS ${mpi_ranks} RUN_SERIAL TRUE TIMEOUT 12 + ) +endforeach() + +add_executable( + mpi_fixed_broadcast_failure_probe + communication/mpi_fixed_broadcast_failure_probe.cpp +) +target_link_libraries( + mpi_fixed_broadcast_failure_probe + PRIVATE parallel kahip_options kahip_warnings +) +add_test( + NAME unit-pmpi-callback-safety-fixed-broadcast-failure + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/communication/mpi_fixed_broadcast_failure_probe.cpp" + "-DPROFILE=fixed-broadcast-failure" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_pmpi_callback_safety.cmake" +) +set_tests_properties( + unit-pmpi-callback-safety-fixed-broadcast-failure + PROPERTIES TIMEOUT 5 +) +foreach( + failure_mode + IN ITEMS + status + header + partition-map + mismatched-map + previous-cut + previous-weight + missing-file + truncated-header + invalid-version + intercommunicator +) + if(failure_mode STREQUAL "status") + set( + expected_diagnostic + "MPI backend failure: MPI_Bcast(binary graph read status)" + ) + elseif(failure_mode STREQUAL "header") + set( + expected_diagnostic + "MPI backend failure: MPI_Bcast(binary graph header)" + ) + elseif(failure_mode STREQUAL "partition-map") + set( + expected_diagnostic + "MPI backend failure: MPI_Bcast(previous partition map)" + ) + elseif(failure_mode STREQUAL "mismatched-map") + set( + expected_diagnostic + "MPI adapter programming failure: bounded broadcast arguments differ across communicator" + ) + elseif(failure_mode STREQUAL "previous-cut") + set( + expected_diagnostic + "MPI backend failure: MPI_Bcast(previous edge cut)" + ) + elseif(failure_mode STREQUAL "previous-weight") + set( + expected_diagnostic + "MPI backend failure: MPI_Bcast(previous maximum block weight)" + ) + elseif(failure_mode STREQUAL "missing-file") + set( + expected_diagnostic + "Distributed backend failure: unable to open binary graph file" + ) + elseif(failure_mode STREQUAL "truncated-header") + set( + expected_diagnostic + "Distributed backend failure: unable to read binary graph header" + ) + elseif(failure_mode STREQUAL "invalid-version") + set( + expected_diagnostic + "Distributed backend failure: unsupported binary graph version" + ) + else() + set( + expected_diagnostic + "MPI adapter programming failure: bounded broadcast requires a live intracommunicator" + ) + endif() + add_test( + NAME unit-mpi-fixed-broadcast-${failure_mode}-failure + COMMAND + "${CMAKE_COMMAND}" + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DPROBE=$" + "-DMODE=${failure_mode}" + "-DEXPECTED_DIAGNOSTIC=${expected_diagnostic}" + "-DFIXTURE=${CMAKE_CURRENT_BINARY_DIR}/fixed-broadcast-${failure_mode}.bgf" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_mpi_fixed_broadcast_failure.cmake" + ) + set_tests_properties( + unit-mpi-fixed-broadcast-${failure_mode}-failure + PROPERTIES PROCESSORS 2 RUN_SERIAL TRUE TIMEOUT 12 + ) +endforeach() + +add_executable(vertex_cut_mpi_test dspac/vertex_cut_mpi_test.cpp) +target_link_libraries( + vertex_cut_mpi_test + PRIVATE + catch_mpi_runner + libdspac + parallel + kahip_options + kahip_warnings +) +add_test( + NAME unit-pmpi-callback-safety-vertex-cut + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/dspac/vertex_cut_mpi_test.cpp" + "-DPROFILE=vertex-cut" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_pmpi_callback_safety.cmake" +) +set_tests_properties( + unit-pmpi-callback-safety-vertex-cut + PROPERTIES TIMEOUT 5 +) +foreach(mpi_ranks RANGE 1 5) + add_test( + NAME unit-dspac-vertex-cut-${mpi_ranks}-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" ${mpi_ranks} + ${MPIEXEC_PREFLAGS} + $ + ${MPIEXEC_POSTFLAGS} + ) + set_tests_properties( + unit-dspac-vertex-cut-${mpi_ranks}-rank + PROPERTIES PROCESSORS ${mpi_ranks} RUN_SERIAL TRUE TIMEOUT 12 + ) +endforeach() + +add_executable(vertex_cut_failure_probe dspac/vertex_cut_failure_probe.cpp) +target_link_libraries( + vertex_cut_failure_probe + PRIVATE libdspac parallel kahip_options kahip_warnings +) +add_test( + NAME unit-pmpi-callback-safety-vertex-cut-failure + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/dspac/vertex_cut_failure_probe.cpp" + "-DPROFILE=vertex-cut-failure" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_pmpi_callback_safety.cmake" +) +set_tests_properties( + unit-pmpi-callback-safety-vertex-cut-failure + PROPERTIES TIMEOUT 5 +) +foreach( + failure_mode + IN ITEMS + backend + zero-k + undersized-partition + out-of-range-label + mismatched-k + intercommunicator +) + if(failure_mode STREQUAL "backend") + set( + expected_diagnostic + "MPI backend failure: MPI_Allreduce(vertex cut)" + ) + elseif(failure_mode STREQUAL "zero-k") + set( + expected_diagnostic + "MPI adapter programming failure: vertex cut requires k greater than zero" + ) + elseif(failure_mode STREQUAL "undersized-partition") + set( + expected_diagnostic + "MPI adapter programming failure: vertex cut partition extent does not match local edge count" + ) + elseif(failure_mode STREQUAL "out-of-range-label") + set( + expected_diagnostic + "MPI adapter programming failure: vertex cut partition label is outside [0, k)" + ) + elseif(failure_mode STREQUAL "mismatched-k") + set( + expected_diagnostic + "MPI adapter programming failure: vertex cut k differs across communicator" + ) + else() + set( + expected_diagnostic + "MPI adapter programming failure: fixed reduction requires a live intracommunicator" + ) + endif() + add_test( + NAME unit-dspac-vertex-cut-${failure_mode}-failure + COMMAND + "${CMAKE_COMMAND}" + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DPROBE=$" + "-DMODE=${failure_mode}" + "-DEXPECTED_DIAGNOSTIC=${expected_diagnostic}" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/dspac/verify_vertex_cut_failure.cmake" + ) + set_tests_properties( + unit-dspac-vertex-cut-${failure_mode}-failure + PROPERTIES PROCESSORS 2 RUN_SERIAL TRUE TIMEOUT 12 + ) +endforeach() + +add_executable( + abort_marker_fake_launcher + communication/abort_marker_fake_launcher.cpp +) +target_link_libraries( + abort_marker_fake_launcher + PRIVATE kahip_options kahip_warnings +) +foreach(probe_kind IN ITEMS fixed-broadcast vertex-cut) + if(probe_kind STREQUAL "fixed-broadcast") + set( + marker_verifier + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_mpi_fixed_broadcast_failure.cmake" + ) + else() + set( + marker_verifier + "${CMAKE_CURRENT_SOURCE_DIR}/dspac/verify_vertex_cut_failure.cmake" + ) + endif() + add_test( + NAME unit-${probe_kind}-abort-marker-count + COMMAND + "${CMAKE_COMMAND}" + "-DVERIFIER=${marker_verifier}" + "-DFAKE_LAUNCHER=$" + "-DPROBE_KIND=${probe_kind}" + "-DWORK_DIRECTORY=${CMAKE_CURRENT_BINARY_DIR}/abort-marker-count/${probe_kind}" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/abort_marker_count_validation_test.cmake" + ) + set_tests_properties( + unit-${probe_kind}-abort-marker-count + PROPERTIES RUN_SERIAL TRUE TIMEOUT 8 + ) +endforeach() + +add_executable(mpi_trace_test communication/mpi_trace_test.cpp) +target_link_libraries( + mpi_trace_test + PRIVATE Catch2::Catch2WithMain parallel kahip_options kahip_warnings +) +catch_discover_tests( + mpi_trace_test + TEST_PREFIX "unit-" + OUTPUT_DIR . + OUTPUT_PREFIX "unit-" +) +add_test( + NAME unit-mpi-trace-oracle-verifier + COMMAND + "${CMAKE_COMMAND}" + "-DVERIFIER=${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_mpi_trace_oracle.cmake" + "-DWORK_DIRECTORY=${CMAKE_CURRENT_BINARY_DIR}/trace-oracle-verifier" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/mpi_trace_oracle_verifier_test.cmake" +) +set_tests_properties( + unit-mpi-trace-oracle-verifier + PROPERTIES TIMEOUT 10 LABELS "oracle;trace" +) + +add_executable( + mpi_trace_failure_probe + communication/mpi_trace_failure_probe.cpp +) +target_compile_definitions( + mpi_trace_failure_probe + PRIVATE KAHIP_ENABLE_MPI_TRACE=1 +) +target_link_libraries( + mpi_trace_failure_probe + PRIVATE parhip_mpi_obj kahip_options kahip_warnings +) +foreach(trace_failure_mode IN ITEMS rank allreduce) + add_test( + NAME unit-mpi-trace-${trace_failure_mode}-backend-failure + COMMAND + "${CMAKE_COMMAND}" + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DMPI_RANKS=2" + "-DPROBE=$" + "-DMODE=${trace_failure_mode}" + "-DEXPECT_FAILURE=TRUE" + "-DEXPECTED_DIAGNOSTIC=MPI backend failure: MPI_" + "-DEXPECTED_DETAIL=original raw code 17411" + "-DEXPECTED_INJECTION=injected trace MPI_" + "-DEXPECTED_MARKER=trace-${trace_failure_mode} affected-communicator" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_mpi_fail_fast_probe.cmake" + ) + set_tests_properties( + unit-mpi-trace-${trace_failure_mode}-backend-failure + PROPERTIES + PROCESSORS 2 + RUN_SERIAL TRUE + TIMEOUT 10 + LABELS "mpi;trace;failure-policy" + ) +endforeach() +if(KAHIP_ENABLE_MPI_TRACE) + add_executable( + mpi_trace_writer_mpi_test + communication/mpi_trace_writer_mpi_test.cpp + ) + target_link_libraries( + mpi_trace_writer_mpi_test + PRIVATE catch_mpi_runner parallel kahip_options kahip_warnings + ) + + function(add_mpi_trace_writer_test test_name catch_test_name) + add_test( + NAME ${test_name} + COMMAND + "${CMAKE_COMMAND}" -E env + "KAHIP_TRACE_WRITER_TEST_BASE=${CMAKE_CURRENT_BINARY_DIR}/trace-writer" + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 2 + ${MPIEXEC_PREFLAGS} + $ + "${catch_test_name}" + ${MPIEXEC_POSTFLAGS} + ) + set_tests_properties( + ${test_name} + PROPERTIES + PROCESSORS 2 + RUN_SERIAL TRUE + TIMEOUT 4 + LABELS "mpi;trace" + ) + endfunction() + + add_mpi_trace_writer_test( + unit-mpi-trace-writer-presence-mismatch + "trace writer rejects rank-local enablement mismatch" + ) + add_mpi_trace_writer_test( + unit-mpi-trace-writer-path-mismatch + "trace writer rejects different rank-local base paths" + ) + add_mpi_trace_writer_test( + unit-mpi-trace-writer-all-unset + "trace writer returns collectively when tracing is unset" + ) + add_mpi_trace_writer_test( + unit-mpi-trace-writer-common-path + "trace writer uses one agreed base path" + ) + + add_test( + NAME integration-mpi-trace-smoke + COMMAND + "${CMAKE_COMMAND}" + "-DPARHIP_EXECUTABLE=$" + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DGRAPH_PATH=${PROJECT_SOURCE_DIR}/examples/rgg_n_2_15_s0.graph" + "-DTRACE_BASE=${CMAKE_CURRENT_BINARY_DIR}/trace-smoke" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/mpi_trace_smoke.cmake" + ) + set_tests_properties( + integration-mpi-trace-smoke + PROPERTIES + PROCESSORS 2 + RUN_SERIAL TRUE + TIMEOUT 60 + LABELS "integration;mpi;trace" + ) +endif() + +foreach(parhip_library IN ITEMS parhip_interface parhip_interface_static) + set(consumer_target "${parhip_library}_build_tree_target_consumer") + add_executable( + ${consumer_target} + interface/parhip_build_tree_target_consumer.cpp + ) + target_compile_features(${consumer_target} PRIVATE cxx_std_23) + target_link_libraries(${consumer_target} PRIVATE ${parhip_library}) + add_test( + NAME "integration-${parhip_library}-build-tree-target" + COMMAND ${consumer_target} + ) + set_tests_properties( + "integration-${parhip_library}-build-tree-target" + PROPERTIES LABELS "integration;interface;mpi;cmake" + ) +endforeach() + +add_executable( + parhip_interface_mpi_test + interface/parhip_interface_mpi_test.cpp +) +target_sources( + parhip_interface_mpi_test + PRIVATE + FILE_SET parhip_configuration_headers + TYPE HEADERS + BASE_DIRS + "${PROJECT_SOURCE_DIR}/parallel/parallel_src/app" + "${PROJECT_SOURCE_DIR}/parallel/parallel_src/lib" + FILES + "${PROJECT_SOURCE_DIR}/parallel/parallel_src/app/configuration.h" + "${PROJECT_SOURCE_DIR}/parallel/parallel_src/lib/communication/mpi_failure.h" + "${PROJECT_SOURCE_DIR}/parallel/parallel_src/lib/communication/mpi_handles.h" + "${PROJECT_SOURCE_DIR}/parallel/parallel_src/lib/communication/serial_kernel_profile_observer.h" +) +target_include_directories( + parhip_interface_mpi_test + PRIVATE + "${PROJECT_SOURCE_DIR}/parallel/parallel_src/lib/tools" + "${PROJECT_SOURCE_DIR}/parallel/shared" +) +target_link_libraries( + parhip_interface_mpi_test + PRIVATE + catch_mpi_runner + parhip_interface + MPI::MPI_CXX + kahip_options + kahip_warnings +) +foreach(mpi_ranks RANGE 1 5) + add_test( + NAME unit-parhip-interface-${mpi_ranks}-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" ${mpi_ranks} + ${MPIEXEC_PREFLAGS} + $ + ${MPIEXEC_POSTFLAGS} + ) + set_tests_properties( + unit-parhip-interface-${mpi_ranks}-rank + PROPERTIES PROCESSORS ${mpi_ranks} RUN_SERIAL TRUE TIMEOUT 30 + ) +endforeach() + +add_executable( + parhip_interface_determinism_probe + interface/parhip_interface_determinism_probe.cpp +) +target_link_libraries( + parhip_interface_determinism_probe + PRIVATE + kahip_cube_fixture_obj + parhip_interface + MPI::MPI_CXX + kahip_options + kahip_warnings +) +add_test( + NAME unit-parhip-interface-repeated-call-determinism + COMMAND + "${CMAKE_COMMAND}" + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DPROBE=$" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/interface/verify_parhip_interface_determinism.cmake" +) +set_tests_properties( + unit-parhip-interface-repeated-call-determinism + PROPERTIES PROCESSORS 2 RUN_SERIAL TRUE TIMEOUT 130 +) + +add_executable( + kaffpae_c_boundary_failure_probe + interface/kaffpae_c_boundary_failure_probe.cpp +) +target_sources( + kaffpae_c_boundary_failure_probe + PRIVATE + FILE_SET modified_kaffpae_interface_headers + TYPE HEADERS + BASE_DIRS "${PROJECT_SOURCE_DIR}/parallel/modified_kahip/interface" + FILES + "${PROJECT_SOURCE_DIR}/parallel/modified_kahip/interface/kaHIP_interface.h" +) +target_link_libraries( + kaffpae_c_boundary_failure_probe + PRIVATE + libmodified_kahip_interface + MPI::MPI_CXX + kahip_fatal_diagnostics + kahip_options + kahip_warnings +) +add_test( + NAME unit-modified-kaffpae-c-boundary-failure + COMMAND + "${CMAKE_COMMAND}" + "-DPROBE=$" + "-DMODE=allocation" + "-DEXPECTED_DIAGNOSTIC=modified kaffpaE C boundary:.*bad_alloc" + "-DEXPECTED_MARKER=observed kaffpaE communicator abort after diagnostic flush" + -P "${CMAKE_CURRENT_SOURCE_DIR}/app/verify_failure_probe.cmake" +) +set_tests_properties( + unit-modified-kaffpae-c-boundary-failure + PROPERTIES + RUN_SERIAL TRUE + TIMEOUT 10 + LABELS "unit;interface;mpi;failure" +) + +add_executable( + parhip_interface_failure_probe + interface/parhip_interface_failure_probe.cpp +) +target_include_directories( + parhip_interface_failure_probe + PRIVATE "${PARHIP_GENERATED_INCLUDE_DIR}" +) +# Keep the probe's MPI wrappers effective on two-level-namespace platforms. +target_link_libraries( + parhip_interface_failure_probe + PRIVATE + parhip_interface_static + MPI::MPI_CXX + kahip_fatal_diagnostics + kahip_options + kahip_warnings +) +add_test( + NAME unit-pmpi-callback-safety-parhip-interface-failure + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/interface/parhip_interface_failure_probe.cpp" + "-DPROFILE=parhip-interface-failure" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_pmpi_callback_safety.cmake" +) +set_tests_properties( + unit-pmpi-callback-safety-parhip-interface-failure + PROPERTIES TIMEOUT 5 +) + +add_executable( + parhip_partition_balance_test + interface/parhip_partition_balance_test.cpp +) +target_link_libraries( + parhip_partition_balance_test + PRIVATE Catch2::Catch2WithMain kahip_options kahip_warnings +) +target_include_directories( + parhip_partition_balance_test + PRIVATE + "${PROJECT_SOURCE_DIR}/parallel/parallel_src" + "${PROJECT_SOURCE_DIR}/parallel/shared" + "${PROJECT_SOURCE_DIR}/parallel" +) +catch_discover_tests( + parhip_partition_balance_test + TEST_PREFIX "unit-" + OUTPUT_DIR . + OUTPUT_PREFIX "unit-" +) + +add_executable( + serial_kernel_profile_test + distributed_partitioning/serial_kernel_profile_test.cpp +) +target_link_libraries( + serial_kernel_profile_test + PRIVATE Catch2::Catch2WithMain kahip_options kahip_warnings +) +target_include_directories( + serial_kernel_profile_test + PRIVATE + "${PROJECT_SOURCE_DIR}/parallel/parallel_src" + "${PROJECT_SOURCE_DIR}/parallel/shared" + "${PROJECT_SOURCE_DIR}/parallel" +) +catch_discover_tests( + serial_kernel_profile_test + TEST_PREFIX "unit-" + OUTPUT_DIR . + OUTPUT_PREFIX "unit-" +) + +add_executable( + serial_kernel_profile_mpi_test + distributed_partitioning/serial_kernel_profile_mpi_test.cpp +) +target_link_libraries( + serial_kernel_profile_mpi_test + PRIVATE catch_mpi_runner parallel kahip_options kahip_warnings +) +foreach(mpi_ranks RANGE 1 5) + add_test( + NAME unit-serial-kernel-profile-agreement-${mpi_ranks}-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" ${mpi_ranks} + ${MPIEXEC_PREFLAGS} + $ + ${MPIEXEC_POSTFLAGS} + ) + set_tests_properties( + unit-serial-kernel-profile-agreement-${mpi_ranks}-rank + PROPERTIES PROCESSORS ${mpi_ranks} RUN_SERIAL TRUE TIMEOUT 15 + LABELS "unit;mpi;serial-kernel;profile" + ) +endforeach() + +foreach( + failure_mode + IN ITEMS + backend-reduction + null-distribution + zero-k + mismatched-k + invalid-imbalance + mismatched-distribution + invalid-offsets + missing-adjacency + invalid-neighbor + mismatched-vertex-weights + missing-partition + invalid-mode + global-weight-overflow + imbalanced-result + intercommunicator +) + if(failure_mode STREQUAL "backend-reduction") + set( + expected_diagnostic + "MPI backend failure: MPI_Allreduce(ParHIP input validation)" + ) + elseif(failure_mode STREQUAL "null-distribution") + set( + expected_diagnostic + "MPI adapter programming failure: ParHIP required input pointers are invalid" + ) + elseif(failure_mode STREQUAL "zero-k") + set( + expected_diagnostic + "MPI adapter programming failure: ParHIP block count must be greater than zero" + ) + elseif(failure_mode STREQUAL "mismatched-k") + set( + expected_diagnostic + "MPI adapter programming failure: ParHIP block count differs across communicator" + ) + elseif(failure_mode STREQUAL "invalid-imbalance") + set( + expected_diagnostic + "MPI adapter programming failure: ParHIP imbalance must be finite and nonnegative" + ) + elseif(failure_mode STREQUAL "mismatched-distribution") + set( + expected_diagnostic + "MPI adapter programming failure: ParHIP vertex distribution differs across communicator" + ) + elseif(failure_mode STREQUAL "invalid-offsets") + set( + expected_diagnostic + "MPI adapter programming failure: ParHIP local CSR offsets are invalid" + ) + elseif(failure_mode STREQUAL "missing-adjacency") + set( + expected_diagnostic + "MPI adapter programming failure: ParHIP adjacency pointer is missing for nonempty edge storage" + ) + elseif(failure_mode STREQUAL "invalid-neighbor") + set( + expected_diagnostic + "MPI adapter programming failure: ParHIP adjacency contains a vertex outside the global domain" + ) + elseif(failure_mode STREQUAL "mismatched-vertex-weights") + set( + expected_diagnostic + "MPI adapter programming failure: ParHIP optional vertex-weight presence differs across communicator" + ) + elseif(failure_mode STREQUAL "missing-partition") + set( + expected_diagnostic + "MPI adapter programming failure: ParHIP partition output pointer is missing for nonempty local storage" + ) + elseif(failure_mode STREQUAL "invalid-mode") + set( + expected_diagnostic + "MPI adapter programming failure: ParHIP mode is outside the supported domain" + ) + elseif(failure_mode STREQUAL "global-weight-overflow") + set( + expected_diagnostic + "MPI adapter capacity failure: ParHIPPartitionKWay: global vertex-weight sum exceeds the graph-weight domain" + ) + elseif(failure_mode STREQUAL "imbalanced-result") + set( + expected_diagnostic + "ParHIP partition balance failure: raw imbalance=0.029999999999999999, effective percentage=3%, normalization status=false, total weight=13, block count=2, configured bound=7, lowest-ID heaviest block=0, actual weight=10, excess=3" + ) + else() + set( + expected_diagnostic + "MPI adapter programming failure: communicator duplication requires a live intracommunicator" + ) + endif() + add_test( + NAME unit-parhip-interface-${failure_mode}-failure + COMMAND + "${CMAKE_COMMAND}" + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DPROBE=$" + "-DMODE=${failure_mode}" + "-DEXPECTED_DIAGNOSTIC=${expected_diagnostic}" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/interface/verify_parhip_interface_failure.cmake" + ) + set_tests_properties( + unit-parhip-interface-${failure_mode}-failure + PROPERTIES PROCESSORS 2 RUN_SERIAL TRUE TIMEOUT 25 + ) +endforeach() + +add_executable( + parhip_abort_marker_fake_launcher + interface/parhip_abort_marker_fake_launcher.cpp +) +target_link_libraries( + parhip_abort_marker_fake_launcher + PRIVATE kahip_options kahip_warnings +) +add_test( + NAME unit-parhip-interface-abort-marker-count + COMMAND + "${CMAKE_COMMAND}" + "-DVERIFIER=${CMAKE_CURRENT_SOURCE_DIR}/interface/verify_parhip_interface_failure.cmake" + "-DFAKE_LAUNCHER=$" + "-DWORK_DIRECTORY=${CMAKE_CURRENT_BINARY_DIR}/abort-marker-count/parhip-interface" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/interface/parhip_abort_marker_count_validation_test.cmake" +) +set_tests_properties( + unit-parhip-interface-abort-marker-count + PROPERTIES RUN_SERIAL TRUE TIMEOUT 8 +) +target_link_libraries( + parallel_contraction_test + PRIVATE kahip_options kahip_warnings +) +target_link_libraries( + parallel_contraction_mpi_test + PRIVATE kahip_options kahip_warnings +) + +add_executable(first_split_mpi_test dspac/first_split_mpi_test.cpp) +target_link_libraries( + first_split_mpi_test + PRIVATE + catch_mpi_runner + libdspac + parallel + kahip_options + kahip_warnings +) +add_test( + NAME unit-pmpi-callback-safety-dspac-first-split + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/dspac/first_split_mpi_test.cpp" + "-DPROFILE=dspac-first-split" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_pmpi_callback_safety.cmake" +) +set_tests_properties( + unit-pmpi-callback-safety-dspac-first-split + PROPERTIES TIMEOUT 5 +) +foreach(mpi_ranks RANGE 1 5) + add_test( + NAME unit-dspac-first-split-${mpi_ranks}-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" ${mpi_ranks} + ${MPIEXEC_PREFLAGS} + $ + ${MPIEXEC_POSTFLAGS} + ) + set_tests_properties( + unit-dspac-first-split-${mpi_ranks}-rank + PROPERTIES PROCESSORS ${mpi_ranks} RUN_SERIAL TRUE TIMEOUT 15 + ) +endforeach() + +add_executable( + first_split_failure_probe + dspac/first_split_failure_probe.cpp +) +target_link_libraries( + first_split_failure_probe + PRIVATE libdspac parallel kahip_options kahip_warnings +) +add_test( + NAME unit-pmpi-callback-safety-dspac-first-split-failure + COMMAND + "${CMAKE_COMMAND}" + "-DSOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/dspac/first_split_failure_probe.cpp" + "-DPROFILE=dspac-first-split-failure" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_pmpi_callback_safety.cmake" +) +set_tests_properties( + unit-pmpi-callback-safety-dspac-first-split-failure + PROPERTIES TIMEOUT 5 +) +foreach( + failure_mode + IN ITEMS neighbor-payload projection-permutation projection-barrier +) + if(failure_mode STREQUAL "neighbor-payload") + set(failure_test_name unit-dspac-first-split-backend-failure) + else() + set(failure_test_name unit-dspac-${failure_mode}-failure) + endif() + add_test( + NAME ${failure_test_name} + COMMAND + "${CMAKE_COMMAND}" + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DPROBE=$" + "-DMODE=${failure_mode}" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/dspac/verify_first_split_failure.cmake" + ) + set_tests_properties( + ${failure_test_name} + PROPERTIES PROCESSORS 2 RUN_SERIAL TRUE TIMEOUT 12 + ) +endforeach() + +add_executable(parallel_io_mpi_test io/parallel_io_mpi_test.cpp) +target_link_libraries( + parallel_io_mpi_test + PRIVATE + catch_mpi_runner + parallel + kahip_options + kahip_warnings +) +foreach(mpi_ranks RANGE 1 5) + add_test( + NAME unit-parallel-io-${mpi_ranks}-rank + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" ${mpi_ranks} + ${MPIEXEC_PREFLAGS} + $ + "[parallel-io]" + ${MPIEXEC_POSTFLAGS} + ) + set_tests_properties( + unit-parallel-io-${mpi_ranks}-rank + PROPERTIES PROCESSORS ${mpi_ranks} RUN_SERIAL TRUE TIMEOUT 15 + ) +endforeach() + +add_executable(parallel_io_failure_probe io/parallel_io_failure_probe.cpp) +target_link_libraries( + parallel_io_failure_probe + PRIVATE parallel kahip_options kahip_warnings +) +foreach( + failure_mode + IN ITEMS + vector-missing + vector-truncated + graph-truncated + text-missing +) + if(failure_mode MATCHES "^vector-") + set( + expected_diagnostic + "Distributed backend failure: partition binary payload I/O failed" + ) + elseif(failure_mode STREQUAL "graph-truncated") + set( + expected_diagnostic + "Distributed backend failure: binary graph payload I/O failed" + ) + else() + set( + expected_diagnostic + "Distributed backend failure: METIS graph I/O failed" + ) + endif() + add_test( + NAME unit-parallel-io-${failure_mode}-failure + COMMAND + "${CMAKE_COMMAND}" + "-DPROBE=$" + "-DMODE=${failure_mode}" + -DEXPECT_FAILURE=ON + -DMPI_RANKS=2 + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DEXPECTED_MARKER=parallel-io-${failure_mode} affected-communicator; internal MPI_Finalize counter is zero" + "-DEXPECTED_DIAGNOSTIC=${expected_diagnostic}" + -P + "${CMAKE_CURRENT_SOURCE_DIR}/communication/verify_mpi_fail_fast_probe.cmake" + ) + set_tests_properties( + unit-parallel-io-${failure_mode}-failure + PROPERTIES PROCESSORS 2 RUN_SERIAL TRUE TIMEOUT 12 + ) +endforeach() diff --git a/parallel/parallel_src/tests/app/application_math_test.cpp b/parallel/parallel_src/tests/app/application_math_test.cpp new file mode 100644 index 00000000..62eac4cd --- /dev/null +++ b/parallel/parallel_src/tests/app/application_math_test.cpp @@ -0,0 +1,69 @@ +#include +#include +#include + +#include "random_state.h" + +namespace { +constexpr auto maximum = std::numeric_limits::max(); + +static_assert(kahip::random_compat::outer_rank_seed(7, 4, 0) == 7); +static_assert(kahip::random_compat::outer_rank_seed(7, 4, 3) == 31); +static_assert(kahip::random_compat::outer_rank_seed(536870912, 2, 0) == + 536870912); +static_assert(kahip::random_compat::outer_rank_seed(536870912, 2, 1) == + 1073741825); + +static_assert(kahip::random_compat::mixed_rank_seed(536870912, 2, 0) == + 1073741824); +static_assert(kahip::random_compat::mixed_rank_seed(1073741825, 2, 1) == + -2147483645); +static_assert(kahip::random_compat::mixed_rank_seed( + std::numeric_limits::max(), 2, 0) == -2); +static_assert(kahip::random_compat::mixed_rank_seed( + std::numeric_limits::max(), 2, 1) == -1); +static_assert(kahip::random_compat::mixed_rank_seed( + std::numeric_limits::min(), 2, 0) == 0); +static_assert(kahip::random_compat::mixed_rank_seed( + std::numeric_limits::min(), 2, 1) == 1); + +static_assert(!kahip::random_compat::mixed_rank_seed(1, 0, 0).has_value()); +static_assert(!kahip::random_compat::mixed_rank_seed(1, -1, 0).has_value()); +static_assert(!kahip::random_compat::mixed_rank_seed(1, 2, -1).has_value()); +static_assert(!kahip::random_compat::mixed_rank_seed(1, 2, 2).has_value()); +static_assert(!kahip::random_compat::outer_rank_seed(1, 0, 0).has_value()); +static_assert(!kahip::random_compat::outer_rank_seed(1, 2, 2).has_value()); + +static_assert(kahip::random_compat::exact_partition_upper_bound( + 10ULL, 3ULL, 3U) == 4ULL); +static_assert(kahip::random_compat::exact_partition_upper_bound( + 103ULL, 4ULL, 100U) == 52ULL); +static_assert(kahip::random_compat::exact_partition_upper_bound( + 200ULL * 200ULL * 200ULL, 256ULL, 3U) == 32187ULL); +static_assert(kahip::random_compat::exact_partition_upper_bound( + 400ULL * 400ULL * 400ULL, 1564ULL, 3U) == 42148ULL); +static_assert(kahip::random_compat::exact_partition_upper_bound( + maximum, 1ULL, 0U) == maximum); +static_assert(!kahip::random_compat::exact_partition_upper_bound( + 1ULL, 0ULL, 3U) + .has_value()); +static_assert(!kahip::random_compat::exact_partition_upper_bound( + maximum, 1ULL, 1U) + .has_value()); + +static_assert([] { + auto value = maximum - 1; + return kahip::random_compat::checked_add(value, 1ULL) && value == maximum; +}()); +static_assert([] { + auto value = maximum; + return !kahip::random_compat::checked_add(value, 1ULL) && value == maximum; +}()); + +static_assert(kahip::random_compat::checked_narrow(42148ULL) == + 42148U); +static_assert(!kahip::random_compat::checked_narrow(maximum) + .has_value()); +} // namespace + +int main() { return EXIT_SUCCESS; } diff --git a/parallel/parallel_src/tests/app/kaffpae_runtime_failure_probe.cpp b/parallel/parallel_src/tests/app/kaffpae_runtime_failure_probe.cpp new file mode 100644 index 00000000..1ae6b84c --- /dev/null +++ b/parallel/parallel_src/tests/app/kaffpae_runtime_failure_probe.cpp @@ -0,0 +1,162 @@ +#include +#include + +#include +#include +#include +#include + +#include "mpi_application_runtime.h" +#include "tools/fatal_diagnostics.h" + +namespace { +enum class failure_mode : unsigned char { rank_query, communicator_free }; + +inline auto selected_mode = failure_mode::rank_query; +inline bool injection_active = false; +inline MPI_Comm operation_communicator = MPI_COMM_NULL; +inline int rank_queries = 0; +inline int communicator_frees = 0; +inline int finalizations = 0; +inline volatile std::sig_atomic_t diagnostic_was_flushed = 0; + +constexpr auto injected_rank_error = 17401; +constexpr auto injected_free_error = 17402; + +void write_text(std::string_view text) noexcept; + +void observe_diagnostic(std::string_view message) noexcept { + write_text(message); + write_text("\n"); +} + +void observe_flush() noexcept { + diagnostic_was_flushed = 1; + write_text("observed synchronous diagnostic flush\n"); +} + +constexpr auto observing_sink = kahip::diagnostics::sink{ + .write = observe_diagnostic, + .flush = observe_flush, +}; + +[[noreturn]] void observed_process_abort(int) noexcept { + if (diagnostic_was_flushed == 0) { + write_text("process aborted before diagnostic flush\n"); + std::_Exit(91); + } + write_text("observed process abort after diagnostic flush\n"); + std::_Exit(86); +} + +// KAHIP_PMPI_CALLBACK_REGION_BEGIN +void write_text(std::string_view text) noexcept { + static_cast(::write(STDERR_FILENO, text.data(), text.size())); +} + +[[nodiscard]] auto operation_communicator_matches(MPI_Comm communicator) noexcept + -> bool { + auto relation = int{MPI_UNEQUAL}; + return communicator != MPI_COMM_WORLD && + operation_communicator != MPI_COMM_NULL && + PMPI_Comm_compare(communicator, operation_communicator, &relation) == + MPI_SUCCESS && + relation == MPI_IDENT; +} +} // namespace + +static_assert(noexcept(write_text({}))); +static_assert(noexcept(operation_communicator_matches(MPI_COMM_NULL))); + +extern "C" int MPI_Comm_rank(MPI_Comm communicator, int* rank) { + if (injection_active && selected_mode == failure_mode::rank_query) { + ++rank_queries; + if (!operation_communicator_matches(communicator) || rank == nullptr) { + write_text("forbidden MPI call: unexpected injected rank query\n"); + return MPI_ERR_OTHER; + } + return injected_rank_error; + } + return PMPI_Comm_rank(communicator, rank); +} + +extern "C" int MPI_Comm_free(MPI_Comm* communicator) { + if (injection_active && selected_mode == failure_mode::communicator_free) { + ++communicator_frees; + if (communicator == nullptr || + !operation_communicator_matches(*communicator)) { + write_text("forbidden MPI call: unexpected communicator free\n"); + return MPI_ERR_OTHER; + } + return injected_free_error; + } + return PMPI_Comm_free(communicator); +} + +extern "C" int MPI_Finalize() { + if (injection_active) { + ++finalizations; + write_text("forbidden MPI call: finalize after injected failure\n"); + return MPI_ERR_OTHER; + } + return PMPI_Finalize(); +} + +extern "C" int MPI_Abort(MPI_Comm communicator, int error_code) { + auto const expected_state = + diagnostic_was_flushed != 0 && error_code == EXIT_FAILURE && + finalizations == 0 && + ((selected_mode == failure_mode::rank_query && rank_queries == 1 && + communicator_frees == 0 && + operation_communicator_matches(communicator)) || + (selected_mode == failure_mode::communicator_free && + rank_queries == 0 && communicator_frees == 1 && + communicator == MPI_COMM_WORLD)); + if (!expected_state) { + write_text("forbidden MPI call: abort used unexpected state or scope\n"); + std::_Exit(92); + } + if (selected_mode == failure_mode::rank_query) { + write_text( + "observed operation communicator MPI_Abort after diagnostic flush\n"); + } else { + write_text( + "observed MPI_COMM_WORLD fallback abort after diagnostic flush\n"); + } + std::_Exit(86); +} +// KAHIP_PMPI_CALLBACK_REGION_END + +int main(int argc, char** argv) { + if (argc != 2) { + return 64; + } + auto const mode = std::string_view{argv[1]}; + if (mode == "rank-query") { + selected_mode = failure_mode::rank_query; + } else if (mode == "communicator-free") { + selected_mode = failure_mode::communicator_free; + } else { + return 64; + } + + static_cast( + kahip::diagnostics::exchange_sink_for_testing(&observing_sink)); + if (std::signal(SIGABRT, observed_process_abort) == SIG_ERR) { + return 70; + } + + kahip::mpi::application_runtime runtime{ + argc, argv, std::string{"root runtime failure probe"}}; + return runtime.execute([](MPI_Comm communicator) -> int { + operation_communicator = communicator; + injection_active = true; + if (selected_mode == failure_mode::rank_query) { + auto rank = -1; + kahip::mpi::check_or_abort( + MPI_Comm_rank(communicator, &rank), communicator, + "root runtime failure probe", "MPI_Comm_rank(kaffpaE)"); + } + return EXIT_SUCCESS; + }); +} diff --git a/parallel/parallel_src/tests/app/mpi_application_runtime_failure_probe.cpp b/parallel/parallel_src/tests/app/mpi_application_runtime_failure_probe.cpp new file mode 100644 index 00000000..e2e729f4 --- /dev/null +++ b/parallel/parallel_src/tests/app/mpi_application_runtime_failure_probe.cpp @@ -0,0 +1,134 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_application.h" +#include "tools/fatal_diagnostics.h" + +namespace { +enum class failure_mode { + initialization, + finalization, + operation_exception, + backend_error, +}; + +auto selected_mode = failure_mode::initialization; +volatile std::sig_atomic_t diagnostic_was_flushed = 0; +constexpr auto injected_initialization_error = 17301; +constexpr auto injected_finalization_error = 17302; + +void write_text(std::string_view text) noexcept { + static_cast(::write(STDERR_FILENO, text.data(), text.size())); +} + +void observe_diagnostic(std::string_view message) noexcept { + write_text(message); + write_text("\n"); +} + +void observe_flush() noexcept { + diagnostic_was_flushed = 1; + write_text("observed synchronous diagnostic flush\n"); +} + +constexpr auto observing_sink = kahip::diagnostics::sink{ + .write = observe_diagnostic, + .flush = observe_flush, +}; + +[[noreturn]] void observed_process_abort(int) noexcept { + if (diagnostic_was_flushed == 0) { + write_text("process aborted before diagnostic flush\n"); + std::_Exit(91); + } + write_text("observed process abort after diagnostic flush\n"); + std::_Exit(86); +} +} // namespace + +extern "C" int MPI_Init(int* argument_count, char*** argument_values) { + if (selected_mode == failure_mode::initialization) { + return injected_initialization_error; + } + return PMPI_Init(argument_count, argument_values); +} + +extern "C" int MPI_Finalize() { + if (selected_mode == failure_mode::finalization) { + return injected_finalization_error; + } + return PMPI_Finalize(); +} + +extern "C" int MPI_Abort(MPI_Comm communicator, int) { + if (selected_mode != failure_mode::operation_exception && + selected_mode != failure_mode::backend_error) { + write_text("unexpected MPI_Abort for lifecycle failure\n"); + std::_Exit(92); + } + if (diagnostic_was_flushed == 0) { + write_text("MPI_Abort occurred before diagnostic flush\n"); + std::_Exit(91); + } + + auto comparison = int{MPI_UNEQUAL}; + if (communicator == MPI_COMM_WORLD || + PMPI_Comm_compare(MPI_COMM_WORLD, communicator, &comparison) != + MPI_SUCCESS || + comparison != MPI_CONGRUENT) { + write_text("MPI_Abort did not target the operation communicator\n"); + std::_Exit(93); + } + write_text( + "observed operation communicator MPI_Abort after diagnostic flush\n"); + std::_Exit(86); +} + +int main(int argc, char** argv) { + if (argc != 2) { + std::fprintf(stderr, + "usage: mpi_application_runtime_failure_probe MODE\n"); + return 64; + } + auto const mode = std::string_view{argv[1]}; + if (mode == "initialization") { + selected_mode = failure_mode::initialization; + } else if (mode == "finalization") { + selected_mode = failure_mode::finalization; + } else if (mode == "operation-exception") { + selected_mode = failure_mode::operation_exception; + } else if (mode == "backend-error") { + selected_mode = failure_mode::backend_error; + } else { + std::fprintf(stderr, "unknown failure mode: %s\n", argv[1]); + return 64; + } + + static_cast( + kahip::diagnostics::exchange_sink_for_testing(&observing_sink)); + if (std::signal(SIGABRT, observed_process_abort) == SIG_ERR) { + std::fputs("could not install SIGABRT handler\n", stderr); + return 70; + } + + parhip::mpi::application_runtime runtime{ + argc, argv, std::string{"runtime failure probe"}}; + return runtime.execute([](parhip::mpi::communicator_view communicator) -> int { + if (selected_mode == failure_mode::operation_exception) { + throw std::runtime_error{"injected operation exception"}; + } + if (selected_mode == failure_mode::backend_error) { + parhip::mpi::abort_on_mpi_error( + communicator.native_handle(), MPI_ERR_OTHER, + "injected application backend error"); + } + return EXIT_SUCCESS; + }); +} diff --git a/parallel/parallel_src/tests/app/mpi_application_runtime_mpi_test.cpp b/parallel/parallel_src/tests/app/mpi_application_runtime_mpi_test.cpp new file mode 100644 index 00000000..42aefe91 --- /dev/null +++ b/parallel/parallel_src/tests/app/mpi_application_runtime_mpi_test.cpp @@ -0,0 +1,48 @@ +#include + +#include +#include + +#include "communication/mpi_application.h" + +namespace { +void verify_finalization() { + auto finalized = 0; + if (MPI_Finalized(&finalized) != MPI_SUCCESS || finalized == 0) { + std::cerr << "application runtime did not finalize MPI\n"; + std::_Exit(EXIT_FAILURE); + } +} +} // namespace + +int main(int argc, char** argv) { + if (std::atexit(verify_finalization) != 0) { + std::cerr << "could not install MPI finalization verifier\n"; + return EXIT_FAILURE; + } + + parhip::mpi::application_runtime runtime{argc, argv, "runtime smoke test"}; + return runtime.execute([](parhip::mpi::communicator_view communicator) { + auto comparison = int{MPI_UNEQUAL}; + parhip::mpi::check_or_abort( + MPI_Comm_compare(MPI_COMM_WORLD, communicator.native_handle(), + &comparison), + communicator.native_handle(), "MPI_Comm_compare(runtime smoke test)"); + if (communicator.native_handle() == MPI_COMM_WORLD || + comparison != MPI_CONGRUENT) { + std::cerr << "operation communicator is not a distinct duplicate\n"; + return EXIT_FAILURE; + } + + auto handler = MPI_Errhandler{}; + parhip::mpi::check_or_abort( + MPI_Comm_get_errhandler(communicator.native_handle(), &handler), + communicator.native_handle(), + "MPI_Comm_get_errhandler(runtime smoke test)"); + if (handler != MPI_ERRORS_RETURN) { + std::cerr << "operation communicator does not return MPI errors\n"; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; + }); +} diff --git a/parallel/parallel_src/tests/app/mpi_finalize_observer.cpp b/parallel/parallel_src/tests/app/mpi_finalize_observer.cpp new file mode 100644 index 00000000..4f5c92a2 --- /dev/null +++ b/parallel/parallel_src/tests/app/mpi_finalize_observer.cpp @@ -0,0 +1,33 @@ +#include + +#include +#include +#include + +namespace { +auto initialized = false; +auto finalized = false; + +void write_text(std::string_view text) noexcept { + static_cast(::write(STDERR_FILENO, text.data(), text.size())); +} + +__attribute__((destructor)) void verify_mpi_was_finalized() { + if (initialized && !finalized) { + write_text("executable returned without MPI_Finalize\n"); + std::_Exit(88); + } +} +} // namespace + +extern "C" int MPI_Init(int* argument_count, char*** argument_values) { + auto const result = PMPI_Init(argument_count, argument_values); + initialized = result == MPI_SUCCESS; + return result; +} + +extern "C" int MPI_Finalize() { + auto const result = PMPI_Finalize(); + finalized = result == MPI_SUCCESS; + return result; +} diff --git a/parallel/parallel_src/tests/app/mpi_owned_handle_lifetime_failure_probe.cpp b/parallel/parallel_src/tests/app/mpi_owned_handle_lifetime_failure_probe.cpp new file mode 100644 index 00000000..7f28854e --- /dev/null +++ b/parallel/parallel_src/tests/app/mpi_owned_handle_lifetime_failure_probe.cpp @@ -0,0 +1,127 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_adapter.h" +#include "tools/fatal_diagnostics.h" + +namespace { +auto after_finalization = false; +volatile std::sig_atomic_t diagnostic_was_flushed = 0; + +void write_text(std::string_view text) noexcept { + static_cast(::write(STDERR_FILENO, text.data(), text.size())); +} + +void observe_diagnostic(std::string_view message) noexcept { + write_text(message); + write_text("\n"); +} + +void observe_flush() noexcept { + diagnostic_was_flushed = 1; + write_text("observed synchronous diagnostic flush\n"); +} + +constexpr auto observing_sink = kahip::diagnostics::sink{ + .write = observe_diagnostic, + .flush = observe_flush, +}; + +[[noreturn]] void forbidden_post_finalize_call(char const* operation) noexcept { + std::fprintf(stderr, "forbidden MPI call after finalization: %s\n", + operation); + std::_Exit(90); +} + +[[noreturn]] void observed_abort(int) noexcept { + if (diagnostic_was_flushed == 0) { + write_text("owned handle aborted before diagnostic flush\n"); + std::_Exit(91); + } + write_text("observed owned-handle abort after diagnostic flush\n"); + std::_Exit(86); +} +} // namespace + +extern "C" int MPI_Finalize() { + auto const result = PMPI_Finalize(); + after_finalization = result == MPI_SUCCESS; + return result; +} + +extern "C" int MPI_Comm_free(MPI_Comm* communicator) { + if (after_finalization) { + forbidden_post_finalize_call("MPI_Comm_free"); + } + return PMPI_Comm_free(communicator); +} + +extern "C" int MPI_Type_free(MPI_Datatype* datatype) { + if (after_finalization) { + forbidden_post_finalize_call("MPI_Type_free"); + } + return PMPI_Type_free(datatype); +} + +extern "C" int MPI_Abort(MPI_Comm communicator, int error_code) { + if (after_finalization) { + forbidden_post_finalize_call("MPI_Abort"); + } + return PMPI_Abort(communicator, error_code); +} + +int main(int argc, char** argv) { + if (argc != 2) { + std::fputs("usage: mpi_owned_handle_lifetime_failure_probe MODE\n", + stderr); + return 64; + } + auto const mode = std::string_view{argv[1]}; + if (mode != "communicator" && mode != "datatype" && + mode != "distributed-graph") { + std::fprintf(stderr, "unknown mode: %s\n", argv[1]); + return 64; + } + static_cast( + kahip::diagnostics::exchange_sink_for_testing(&observing_sink)); + if (std::signal(SIGABRT, observed_abort) == SIG_ERR) { + return 70; + } + if (MPI_Init(&argc, &argv) != MPI_SUCCESS) { + return 70; + } + + auto communicator = std::unique_ptr{}; + auto datatype = std::unique_ptr{}; + auto graph = std::unique_ptr{}; + if (mode == "communicator") { + communicator = std::make_unique( + parhip::mpi::communicator_view{MPI_COMM_WORLD}); + } else if (mode == "datatype") { + auto handle = MPI_DATATYPE_NULL; + if (MPI_Type_contiguous(2, MPI_INT, &handle) != MPI_SUCCESS || + MPI_Type_commit(&handle) != MPI_SUCCESS) { + return 70; + } + datatype = std::make_unique( + parhip::mpi::datatype::owned(handle)); + } else { + graph = std::make_unique( + parhip::mpi::communicator_view{MPI_COMM_WORLD}, std::vector{}); + } + + if (MPI_Finalize() != MPI_SUCCESS) { + return 70; + } + communicator.reset(); + datatype.reset(); + graph.reset(); + return 2; +} diff --git a/parallel/parallel_src/tests/app/parser_communicator_mpi_test.cpp b/parallel/parallel_src/tests/app/parser_communicator_mpi_test.cpp new file mode 100644 index 00000000..61f6f74f --- /dev/null +++ b/parallel/parallel_src/tests/app/parser_communicator_mpi_test.cpp @@ -0,0 +1,99 @@ +#include +#include + +#include +#include +#include + +#include "communication/mpi_handles.h" +#include "parse_dspac_parameters.h" +#include "parse_parameters.h" + +namespace { +auto reject_world_queries = false; + +[[noreturn]] void reject_world(char const* operation) noexcept { + std::fprintf(stderr, "%s queried MPI_COMM_WORLD inside parser\n", operation); + std::_Exit(87); +} +} // namespace + +extern "C" int MPI_Comm_rank(MPI_Comm communicator, int* rank) { + if (reject_world_queries && communicator == MPI_COMM_WORLD) { + reject_world("MPI_Comm_rank"); + } + return PMPI_Comm_rank(communicator, rank); +} + +extern "C" int MPI_Comm_size(MPI_Comm communicator, int* size) { + if (reject_world_queries && communicator == MPI_COMM_WORLD) { + reject_world("MPI_Comm_size"); + } + return PMPI_Comm_size(communicator, size); +} + +int main(int argc, char** argv) { + if (PMPI_Init(&argc, &argv) != MPI_SUCCESS) { + std::fputs("MPI_Init failed\n", stderr); + return 70; + } + + auto world_rank = 0; + PMPI_Comm_rank(MPI_COMM_WORLD, &world_rank); + auto reversed = MPI_COMM_NULL; + if (PMPI_Comm_split(MPI_COMM_WORLD, 0, -world_rank, &reversed) != + MPI_SUCCESS) { + std::fputs("MPI_Comm_split failed\n", stderr); + PMPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE); + } + PMPI_Comm_set_errhandler(reversed, MPI_ERRORS_RETURN); + auto const communicator = parhip::mpi::communicator_view{reversed}; + + char parhip_program[] = "parser-test"; + char parhip_graph[] = "graph.graph"; + char parhip_k[] = "--k=4"; + char parhip_preconfiguration[] = "--preconfiguration=ecosocial"; + char* parhip_arguments[]{parhip_program, parhip_graph, parhip_k, + parhip_preconfiguration}; + auto parhip_config = parhip::PPartitionConfig{}; + auto graph_filename = std::string{}; + + reject_world_queries = true; + auto const parhip_result = parhip::parse_parameters( + static_cast(std::size(parhip_arguments)), parhip_arguments, + parhip_config, graph_filename, communicator); + reject_world_queries = false; + + char dspac_program[] = "dspac-parser-test"; + char dspac_graph[] = "graph.graph"; + char dspac_k[] = "--k=4"; + char dspac_preconfiguration[] = "--preconfiguration=ecosocial"; + char* dspac_arguments[]{dspac_program, dspac_graph, dspac_k, + dspac_preconfiguration}; + auto dspac_partition_config = parhip::PPartitionConfig{}; + auto dspac_config = parhip::DspacConfig{}; + auto dspac_graph_filename = std::string{}; + auto dspac_partition_filename = std::string{}; + + reject_world_queries = true; + auto const dspac_result = parhip::parse_dspac_parameters( + static_cast(std::size(dspac_arguments)), dspac_arguments, + dspac_partition_config, dspac_config, dspac_graph_filename, + dspac_partition_filename, communicator); + reject_world_queries = false; + + auto process_count = 0; + PMPI_Comm_size(reversed, &process_count); + auto const valid = + parhip_result == parhip::parse_outcome::continue_execution && + graph_filename == "graph.graph" && + parhip_config.k == 4 && + parhip_config.evolutionary_time_limit == 2048 / process_count && + dspac_result == parhip::parse_outcome::continue_execution && + dspac_graph_filename == "graph.graph" && + dspac_partition_config.k == 4 && dspac_config.k == 4; + + PMPI_Comm_free(&reversed); + PMPI_Finalize(); + return valid ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/parallel/parallel_src/tests/app/verify_executable_finalizes.cmake b/parallel/parallel_src/tests/app/verify_executable_finalizes.cmake new file mode 100644 index 00000000..aebe21c3 --- /dev/null +++ b/parallel/parallel_src/tests/app/verify_executable_finalizes.cmake @@ -0,0 +1,23 @@ +if(NOT DEFINED EXECUTABLE OR NOT DEFINED OBSERVER) + message(FATAL_ERROR "EXECUTABLE and OBSERVER are required") +endif() + +execute_process( + COMMAND + "${CMAKE_COMMAND}" -E env "LD_PRELOAD=${OBSERVER}" "${EXECUTABLE}" + --help + RESULT_VARIABLE executable_result + OUTPUT_VARIABLE executable_stdout + ERROR_VARIABLE executable_stderr + TIMEOUT 10 +) +set(executable_output "${executable_stdout}\n${executable_stderr}") +if(NOT "${executable_result}" STREQUAL "0") + message( + FATAL_ERROR + "help path returned ${executable_result}\n${executable_output}" + ) +endif() +if(executable_output MATCHES "returned without MPI_Finalize") + message(FATAL_ERROR "${executable_output}") +endif() diff --git a/parallel/parallel_src/tests/app/verify_failure_probe.cmake b/parallel/parallel_src/tests/app/verify_failure_probe.cmake new file mode 100644 index 00000000..bd1d59a1 --- /dev/null +++ b/parallel/parallel_src/tests/app/verify_failure_probe.cmake @@ -0,0 +1,38 @@ +if( + NOT DEFINED PROBE + OR NOT DEFINED MODE + OR NOT DEFINED EXPECTED_DIAGNOSTIC + OR NOT DEFINED EXPECTED_MARKER +) + message( + FATAL_ERROR + "PROBE, MODE, EXPECTED_DIAGNOSTIC, and EXPECTED_MARKER are required" + ) +endif() + +execute_process( + COMMAND "${PROBE}" "${MODE}" + RESULT_VARIABLE probe_result + OUTPUT_VARIABLE probe_stdout + ERROR_VARIABLE probe_stderr + TIMEOUT 10 +) +set(probe_output "${probe_stdout}\n${probe_stderr}") +if(NOT "${probe_result}" STREQUAL "86") + message( + FATAL_ERROR + "failure probe returned ${probe_result}, expected 86\n${probe_output}" + ) +endif() +if(NOT probe_output MATCHES "${EXPECTED_DIAGNOSTIC}") + message(FATAL_ERROR "missing fail-fast diagnostic\n${probe_output}") +endif() +if(NOT probe_output MATCHES "observed synchronous diagnostic flush") + message(FATAL_ERROR "diagnostic was not synchronously flushed\n${probe_output}") +endif() +if(NOT probe_output MATCHES "${EXPECTED_MARKER}") + message(FATAL_ERROR "missing termination marker\n${probe_output}") +endif() +if(probe_output MATCHES "forbidden MPI call") + message(FATAL_ERROR "${probe_output}") +endif() diff --git a/parallel/parallel_src/tests/app/verify_kaffpae_invalid_k.cmake b/parallel/parallel_src/tests/app/verify_kaffpae_invalid_k.cmake new file mode 100644 index 00000000..af914af1 --- /dev/null +++ b/parallel/parallel_src/tests/app/verify_kaffpae_invalid_k.cmake @@ -0,0 +1,24 @@ +if(NOT DEFINED KAFFPAE) + message(FATAL_ERROR "KAFFPAE is required") +endif() + +execute_process( + COMMAND "${KAFFPAE}" missing.graph --k=0 + RESULT_VARIABLE kaffpae_result + OUTPUT_VARIABLE kaffpae_stdout + ERROR_VARIABLE kaffpae_stderr + TIMEOUT 10 +) +set(kaffpae_output "${kaffpae_stdout}\n${kaffpae_stderr}") +if("${kaffpae_result}" STREQUAL "0") + message(FATAL_ERROR "invalid k incorrectly returned success\n${kaffpae_output}") +endif() +if(NOT "${kaffpae_result}" MATCHES "^[0-9]+$") + message( + FATAL_ERROR + "invalid k did not return a normal failure status (result=${kaffpae_result})\n${kaffpae_output}" + ) +endif() +if(NOT kaffpae_output MATCHES "Number of blocks must be a positive int") + message(FATAL_ERROR "missing invalid-k diagnostic\n${kaffpae_output}") +endif() diff --git a/parallel/parallel_src/tests/app/verify_parhip_invalid_k.cmake b/parallel/parallel_src/tests/app/verify_parhip_invalid_k.cmake new file mode 100644 index 00000000..8753ed75 --- /dev/null +++ b/parallel/parallel_src/tests/app/verify_parhip_invalid_k.cmake @@ -0,0 +1,31 @@ +if(NOT DEFINED PARHIP) + message(FATAL_ERROR "PARHIP is required") +endif() + +execute_process( + COMMAND + "${PARHIP}" missing.graph --k=0 --preconfiguration=fastmesh + RESULT_VARIABLE parhip_result + OUTPUT_VARIABLE parhip_stdout + ERROR_VARIABLE parhip_stderr + TIMEOUT 10 +) +set(parhip_output "${parhip_stdout}\n${parhip_stderr}") +if("${parhip_result}" STREQUAL "0") + message( + FATAL_ERROR + "invalid k incorrectly returned success\n${parhip_output}" + ) +endif() +if(NOT "${parhip_result}" MATCHES "^[0-9]+$") + message( + FATAL_ERROR + "invalid k did not return a normal failure status (result=${parhip_result})\n${parhip_output}" + ) +endif() +if(NOT parhip_output MATCHES "Number of blocks must be positive") + message(FATAL_ERROR "missing invalid-k diagnostic\n${parhip_output}") +endif() +if(parhip_output MATCHES "Floating point exception|divide-by-zero") + message(FATAL_ERROR "invalid k reached arithmetic\n${parhip_output}") +endif() diff --git a/parallel/parallel_src/tests/catch_mpi/catch_mpi_runner.cpp b/parallel/parallel_src/tests/catch_mpi/catch_mpi_runner.cpp new file mode 100644 index 00000000..9fee1a94 --- /dev/null +++ b/parallel/parallel_src/tests/catch_mpi/catch_mpi_runner.cpp @@ -0,0 +1,25 @@ +// +// Created by Erich Essmann on 16/08/2024. +// +#include +#include + +int main(int argc, char* argv[]) { + Catch::Session session; // There must be exactly one instance + // writing to session.configData() here sets defaults + // this is the preferred way to set them + int returnCode = session.applyCommandLine(argc, argv); + if (returnCode != 0) // Indicates a command line error + return returnCode; + // Every rank must enter collective-bearing tests in the same order and + // use the same generator seed. + session.configData().runOrder = Catch::TestRunOrder::Declared; + session.configData().rngSeed = 1; + // global setup... + MPI_Init(&argc, &argv); + MPI_Comm_set_errhandler(MPI_COMM_WORLD, MPI_ERRORS_RETURN); + int result = session.run(); + // global clean-up... + MPI_Finalize(); + return result; +} diff --git a/parallel/parallel_src/tests/communication/abort_marker_count_validation_test.cmake b/parallel/parallel_src/tests/communication/abort_marker_count_validation_test.cmake new file mode 100644 index 00000000..8eaa5617 --- /dev/null +++ b/parallel/parallel_src/tests/communication/abort_marker_count_validation_test.cmake @@ -0,0 +1,72 @@ +cmake_minimum_required(VERSION 4.0) + +if( + NOT DEFINED VERIFIER + OR NOT DEFINED FAKE_LAUNCHER + OR NOT DEFINED PROBE_KIND + OR NOT DEFINED WORK_DIRECTORY +) + message( + FATAL_ERROR + "VERIFIER, FAKE_LAUNCHER, PROBE_KIND, and WORK_DIRECTORY are required" + ) +endif() + +file(MAKE_DIRECTORY "${WORK_DIRECTORY}") +if(PROBE_KIND STREQUAL "fixed-broadcast") + set(expected_diagnostic "synthetic fixed-broadcast diagnostic") + execute_process( + COMMAND + "${CMAKE_COMMAND}" + "-DMPIEXEC_EXECUTABLE=${FAKE_LAUNCHER}" + "-DMPIEXEC_NUMPROC_FLAG=--ranks" + "-DPROBE=fixed-broadcast" + "-DMODE=status" + "-DEXPECTED_DIAGNOSTIC=${expected_diagnostic}" + "-DFIXTURE=${WORK_DIRECTORY}/unused.bgf" + -P + "${VERIFIER}" + RESULT_VARIABLE verifier_result + OUTPUT_VARIABLE verifier_stdout + ERROR_VARIABLE verifier_stderr + TIMEOUT 5 + ) +elseif(PROBE_KIND STREQUAL "vertex-cut") + execute_process( + COMMAND + "${CMAKE_COMMAND}" + "-DMPIEXEC_EXECUTABLE=${FAKE_LAUNCHER}" + "-DMPIEXEC_NUMPROC_FLAG=--ranks" + "-DPROBE=vertex-cut" + "-DMODE=backend" + "-DEXPECTED_DIAGNOSTIC=synthetic vertex-cut diagnostic" + -P + "${VERIFIER}" + RESULT_VARIABLE verifier_result + OUTPUT_VARIABLE verifier_stdout + ERROR_VARIABLE verifier_stderr + TIMEOUT 5 + ) +else() + message(FATAL_ERROR "unknown PROBE_KIND: ${PROBE_KIND}") +endif() + +set(verifier_output "${verifier_stdout}\n${verifier_stderr}") +if("${verifier_result}" STREQUAL "0") + message( + FATAL_ERROR + "${PROBE_KIND} verifier accepted a single abort marker\n${verifier_output}" + ) +endif() +string( + FIND + "${verifier_output}" + "expected exactly 2 affected-communicator abort markers; found 1" + marker_count_diagnostic +) +if(marker_count_diagnostic EQUAL -1) + message( + FATAL_ERROR + "${PROBE_KIND} verifier failed for the wrong reason\n${verifier_output}" + ) +endif() diff --git a/parallel/parallel_src/tests/communication/abort_marker_fake_launcher.cpp b/parallel/parallel_src/tests/communication/abort_marker_fake_launcher.cpp new file mode 100644 index 00000000..fc90e859 --- /dev/null +++ b/parallel/parallel_src/tests/communication/abort_marker_fake_launcher.cpp @@ -0,0 +1,19 @@ +#include +#include + +int main(int argc, char** argv) { + auto fixed_broadcast = false; + for (auto index = 1; index < argc; ++index) { + fixed_broadcast = + fixed_broadcast || std::string_view{argv[index]} == "fixed-broadcast"; + } + + if (fixed_broadcast) { + std::cerr << "observed fixed-broadcast MPI_Abort on affected communicator\n" + << "MPI backend failure: synthetic fixed-broadcast diagnostic\n"; + } else { + std::cerr << "observed vertex-cut MPI_Abort on affected communicator\n" + << "MPI backend failure: synthetic vertex-cut diagnostic\n"; + } + return 86; +} diff --git a/parallel/parallel_src/tests/communication/evolutionary_collectives_failure_probe.cpp b/parallel/parallel_src/tests/communication/evolutionary_collectives_failure_probe.cpp new file mode 100644 index 00000000..c9eb7e72 --- /dev/null +++ b/parallel/parallel_src/tests/communication/evolutionary_collectives_failure_probe.cpp @@ -0,0 +1,448 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "parallel_mh/evolutionary_collectives.h" + +// KAHIP_PMPI_CALLBACK_REGION_BEGIN +namespace evolutionary_failure_probe { +enum class mode : unsigned char { + permutation, + rank, + communicator_size, + feasibility, + objective, + weight, + root, + signature_minimum, + signature_maximum, + signature_validity, + payload, + count_mismatch, + null_payload, +}; + +inline bool active = false; +inline mode selected = mode::objective; +inline MPI_Comm expected_communicator = MPI_COMM_NULL; +inline int all_reductions = 0; +inline int broadcasts = 0; +inline int rank_queries = 0; +inline int size_queries = 0; +inline bool invalid_mpi_call = false; +inline bool forbidden_communication = false; + +[[nodiscard]] auto objective_datatype() noexcept -> MPI_Datatype { + if constexpr (std::is_same_v) { + return MPI_LONG; + } else { + static_assert(std::is_same_v || + std::is_same_v); + return MPI_LONG_LONG_INT; + } +} + +[[nodiscard]] auto valid_allreduce(int index, + void const* send_buffer, + void* receive_buffer, + int count, + MPI_Datatype datatype, + MPI_Op reduction, + MPI_Comm communicator) noexcept -> bool { + if (communicator != expected_communicator || send_buffer == nullptr || + receive_buffer == nullptr) { + return false; + } + switch (index) { + case 1: + return count == 1 && datatype == MPI_INT && reduction == MPI_MIN; + case 2: + return count == 1 && datatype == objective_datatype() && + reduction == MPI_MIN; + case 3: + return count == 1 && datatype == MPI_UNSIGNED && reduction == MPI_MIN; + case 4: + case 7: + return count == 1 && datatype == MPI_INT && reduction == MPI_MIN; + case 5: + return count == 4 && datatype == MPI_UINT64_T && reduction == MPI_MIN; + case 6: + return count == 4 && datatype == MPI_UINT64_T && reduction == MPI_MAX; + default: + return false; + } +} + +[[nodiscard]] auto expected_abort_state() noexcept -> bool { + if (invalid_mpi_call || forbidden_communication) { + return false; + } + switch (selected) { + case mode::permutation: + return rank_queries == 0 && size_queries == 0 && all_reductions == 0 && + broadcasts == 1; + case mode::rank: + return rank_queries == 1 && size_queries == 0 && all_reductions == 0 && + broadcasts == 0; + case mode::feasibility: + return rank_queries == 1 && size_queries == 0 && all_reductions == 1 && + broadcasts == 0; + case mode::objective: + return rank_queries == 1 && size_queries == 0 && all_reductions == 2 && + broadcasts == 0; + case mode::weight: + return rank_queries == 1 && size_queries == 0 && all_reductions == 3 && + broadcasts == 0; + case mode::root: + return rank_queries == 1 && size_queries == 0 && all_reductions == 4 && + broadcasts == 0; + case mode::communicator_size: + return rank_queries == 1 && size_queries == 1 && all_reductions == 4 && + broadcasts == 0; + case mode::signature_minimum: + return rank_queries == 1 && size_queries == 1 && all_reductions == 5 && + broadcasts == 0; + case mode::signature_maximum: + return rank_queries == 1 && size_queries == 1 && all_reductions == 6 && + broadcasts == 0; + case mode::signature_validity: + return rank_queries == 1 && size_queries == 1 && all_reductions == 7 && + broadcasts == 0; + case mode::payload: + return rank_queries == 1 && size_queries == 1 && all_reductions == 7 && + broadcasts == 1; + case mode::count_mismatch: + case mode::null_payload: + return rank_queries == 1 && size_queries == 1 && all_reductions == 7 && + broadcasts == 0; + } + return false; +} + +void write_text(std::string_view text) noexcept { + static_cast(::write(STDERR_FILENO, text.data(), text.size())); +} + +[[noreturn]] void observed_abort(MPI_Comm communicator, + int error_code) noexcept { + int relation = MPI_UNEQUAL; + if (error_code != EXIT_FAILURE || + PMPI_Comm_compare(communicator, expected_communicator, &relation) != + MPI_SUCCESS || + relation != MPI_IDENT || !expected_abort_state()) { + write_text("observed evolutionary MPI_Abort with unexpected state\n"); + std::_Exit(91); + } + write_text("observed evolutionary MPI_Abort on affected communicator\n"); + std::_Exit(86); +} +} // namespace evolutionary_failure_probe + +static_assert(noexcept(evolutionary_failure_probe::write_text({}))); +static_assert(noexcept(evolutionary_failure_probe::observed_abort(MPI_COMM_NULL, + 0))); +static_assert(noexcept(evolutionary_failure_probe::objective_datatype())); +static_assert( + noexcept(evolutionary_failure_probe::valid_allreduce(0, + nullptr, + nullptr, + 0, + MPI_DATATYPE_NULL, + MPI_OP_NULL, + MPI_COMM_NULL))); +static_assert(noexcept(evolutionary_failure_probe::expected_abort_state())); + +extern "C" int MPI_Comm_rank(MPI_Comm communicator, int* rank) { + if (evolutionary_failure_probe::active) { + ++evolutionary_failure_probe::rank_queries; + if (communicator != evolutionary_failure_probe::expected_communicator || + rank == nullptr) { + evolutionary_failure_probe::invalid_mpi_call = true; + } + if (evolutionary_failure_probe::selected == + evolutionary_failure_probe::mode::rank) { + return MPI_ERR_OTHER; + } + } + return PMPI_Comm_rank(communicator, rank); +} + +extern "C" int MPI_Allreduce(void const* send_buffer, + void* receive_buffer, + int count, + MPI_Datatype datatype, + MPI_Op reduction, + MPI_Comm communicator) { + if (evolutionary_failure_probe::active) { + auto const index = ++evolutionary_failure_probe::all_reductions; + if (!evolutionary_failure_probe::valid_allreduce( + index, send_buffer, receive_buffer, count, datatype, reduction, + communicator)) { + evolutionary_failure_probe::invalid_mpi_call = true; + return MPI_ERR_OTHER; + } + auto const selected = evolutionary_failure_probe::selected; + if ((selected == evolutionary_failure_probe::mode::feasibility && + index == 1) || + (selected == evolutionary_failure_probe::mode::objective && + index == 2) || + (selected == evolutionary_failure_probe::mode::weight && index == 3) || + (selected == evolutionary_failure_probe::mode::root && index == 4) || + (selected == evolutionary_failure_probe::mode::signature_minimum && + index == 5) || + (selected == evolutionary_failure_probe::mode::signature_maximum && + index == 6) || + (selected == evolutionary_failure_probe::mode::signature_validity && + index == 7)) { + return MPI_ERR_OTHER; + } + } + return PMPI_Allreduce(send_buffer, receive_buffer, count, datatype, reduction, + communicator); +} + +extern "C" int MPI_Bcast(void* buffer, + int count, + MPI_Datatype datatype, + int root, + MPI_Comm communicator) { + if (evolutionary_failure_probe::active) { + ++evolutionary_failure_probe::broadcasts; + auto const selected = evolutionary_failure_probe::selected; + auto const expected_payload = + selected == evolutionary_failure_probe::mode::permutation || + selected == evolutionary_failure_probe::mode::payload; + if (!expected_payload || count != 1 || datatype != MPI_UNSIGNED || + root != 0 || + communicator != evolutionary_failure_probe::expected_communicator || + buffer == nullptr) { + evolutionary_failure_probe::invalid_mpi_call = true; + return MPI_ERR_OTHER; + } + if (selected == evolutionary_failure_probe::mode::permutation || + selected == evolutionary_failure_probe::mode::payload) { + return MPI_ERR_OTHER; + } + } + return PMPI_Bcast(buffer, count, datatype, root, communicator); +} + +#if KAHIP_HAVE_MPI_BCAST_C +extern "C" int MPI_Bcast_c(void* buffer, + MPI_Count count, + MPI_Datatype datatype, + int root, + MPI_Comm communicator) { + if (evolutionary_failure_probe::active) { + ++evolutionary_failure_probe::broadcasts; + if (evolutionary_failure_probe::selected != + evolutionary_failure_probe::mode::payload || + count != 1 || datatype != MPI_UNSIGNED || root != 0 || + communicator != evolutionary_failure_probe::expected_communicator || + buffer == nullptr) { + evolutionary_failure_probe::invalid_mpi_call = true; + } + return MPI_ERR_OTHER; + } + return PMPI_Bcast_c(buffer, count, datatype, root, communicator); +} +#endif + +extern "C" int MPI_Comm_size(MPI_Comm communicator, int* size) { + if (evolutionary_failure_probe::active) { + ++evolutionary_failure_probe::size_queries; + if (communicator != evolutionary_failure_probe::expected_communicator || + size == nullptr) { + evolutionary_failure_probe::invalid_mpi_call = true; + } + if (evolutionary_failure_probe::selected == + evolutionary_failure_probe::mode::communicator_size) { + return MPI_ERR_OTHER; + } + } + return PMPI_Comm_size(communicator, size); +} + +extern "C" int MPI_Send(void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator) { + if (evolutionary_failure_probe::active) { + evolutionary_failure_probe::forbidden_communication = true; + return MPI_ERR_OTHER; + } + return PMPI_Send(buffer, count, datatype, destination, tag, communicator); +} + +extern "C" int MPI_Isend(void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator, + MPI_Request* request) { + if (evolutionary_failure_probe::active) { + evolutionary_failure_probe::forbidden_communication = true; + if (request != nullptr) { + *request = MPI_REQUEST_NULL; + } + return MPI_ERR_OTHER; + } + return PMPI_Isend(buffer, count, datatype, destination, tag, communicator, + request); +} + +extern "C" int MPI_Recv(void* buffer, + int count, + MPI_Datatype datatype, + int source, + int tag, + MPI_Comm communicator, + MPI_Status* status) { + if (evolutionary_failure_probe::active) { + evolutionary_failure_probe::forbidden_communication = true; + return MPI_ERR_OTHER; + } + return PMPI_Recv(buffer, count, datatype, source, tag, communicator, status); +} + +extern "C" int MPI_Irecv(void* buffer, + int count, + MPI_Datatype datatype, + int source, + int tag, + MPI_Comm communicator, + MPI_Request* request) { + if (evolutionary_failure_probe::active) { + evolutionary_failure_probe::forbidden_communication = true; + if (request != nullptr) { + *request = MPI_REQUEST_NULL; + } + return MPI_ERR_OTHER; + } + return PMPI_Irecv(buffer, count, datatype, source, tag, communicator, + request); +} + +extern "C" int MPI_Sendrecv(void const* send_buffer, + int send_count, + MPI_Datatype send_datatype, + int destination, + int send_tag, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_datatype, + int source, + int receive_tag, + MPI_Comm communicator, + MPI_Status* status) { + if (evolutionary_failure_probe::active) { + evolutionary_failure_probe::forbidden_communication = true; + return MPI_ERR_OTHER; + } + return PMPI_Sendrecv(send_buffer, send_count, send_datatype, destination, + send_tag, receive_buffer, receive_count, + receive_datatype, source, receive_tag, communicator, + status); +} + +extern "C" int MPI_Barrier(MPI_Comm communicator) { + if (evolutionary_failure_probe::active) { + evolutionary_failure_probe::forbidden_communication = true; + return MPI_ERR_OTHER; + } + return PMPI_Barrier(communicator); +} + +extern "C" int MPI_Abort(MPI_Comm communicator, int error_code) { + evolutionary_failure_probe::observed_abort(communicator, error_code); +} +// KAHIP_PMPI_CALLBACK_REGION_END + +namespace { +auto parse_mode(std::string_view value) -> evolutionary_failure_probe::mode { + using mode = evolutionary_failure_probe::mode; + if (value == "permutation") + return mode::permutation; + if (value == "rank") + return mode::rank; + if (value == "communicator-size") + return mode::communicator_size; + if (value == "feasibility") + return mode::feasibility; + if (value == "objective") + return mode::objective; + if (value == "weight") + return mode::weight; + if (value == "root") + return mode::root; + if (value == "signature-minimum") + return mode::signature_minimum; + if (value == "signature-maximum") + return mode::signature_maximum; + if (value == "signature-validity") + return mode::signature_validity; + if (value == "payload") + return mode::payload; + if (value == "count-mismatch") + return mode::count_mismatch; + if (value == "null-payload") + return mode::null_payload; + std::_Exit(4); +} +} // namespace + +int main(int argc, char** argv) { + if (argc != 2 || MPI_Init(&argc, &argv) != MPI_SUCCESS) { + return 2; + } + auto communicator = MPI_COMM_NULL; + if (PMPI_Comm_dup(MPI_COMM_WORLD, &communicator) != MPI_SUCCESS || + communicator == MPI_COMM_NULL || + PMPI_Comm_set_errhandler(communicator, MPI_ERRORS_RETURN) != + MPI_SUCCESS) { + return 3; + } + + evolutionary_failure_probe::selected = parse_mode(argv[1]); + evolutionary_failure_probe::expected_communicator = communicator; + evolutionary_failure_probe::active = true; + + using mode = evolutionary_failure_probe::mode; + if (evolutionary_failure_probe::selected == mode::permutation) { + auto permutation = std::array{0}; + kahip::parallel_mh::broadcast_permutation(communicator, permutation, 0); + } + + constexpr auto objective = + static_cast(std::numeric_limits::max()) + 4096; + constexpr auto weight = + static_cast(std::numeric_limits::max()) + 1024U; + auto rank = -1; + if (PMPI_Comm_rank(communicator, &rank) != MPI_SUCCESS) { + return 5; + } + auto partition = std::array{weight, weight + 1U}; + auto const count = + evolutionary_failure_probe::selected == mode::count_mismatch && rank != 0 + ? std::size_t{2} + : std::size_t{1}; + auto* partition_data = + evolutionary_failure_probe::selected == mode::null_payload + ? static_cast(nullptr) + : partition.data(); + static_cast(kahip::parallel_mh::select_and_broadcast_best_partition( + communicator, objective, weight, weight - 1U, partition_data, count)); + + evolutionary_failure_probe::write_text( + "evolutionary collective returned without fail-fast\n"); + std::_Exit(92); +} diff --git a/parallel/parallel_src/tests/communication/evolutionary_collectives_mpi_test.cpp b/parallel/parallel_src/tests/communication/evolutionary_collectives_mpi_test.cpp new file mode 100644 index 00000000..c44f1327 --- /dev/null +++ b/parallel/parallel_src/tests/communication/evolutionary_collectives_mpi_test.cpp @@ -0,0 +1,556 @@ +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "parallel_mh/evolutionary_collectives.h" + +// KAHIP_PMPI_CALLBACK_REGION_BEGIN +namespace evolutionary_probe { +enum class operation_kind : unsigned char { all_reduce, broadcast }; + +struct operation final { + operation_kind kind{}; + MPI_Count count = 0; + MPI_Datatype datatype = MPI_DATATYPE_NULL; + MPI_Op reduction = MPI_OP_NULL; + int root = -1; + MPI_Comm communicator = MPI_COMM_NULL; +}; + +struct counters final { + std::array operations{}; + int operation_count = 0; + int rank_queries = 0; + int size_queries = 0; + bool overflow = false; + bool invalid_collective = false; + bool forbidden_communication = false; +}; + +inline bool active = false; +inline bool suppress_large_count_payload = false; +inline MPI_Comm expected_communicator = MPI_COMM_NULL; +inline counters observed{}; + +void reset() noexcept { + observed = {}; + suppress_large_count_payload = false; +} + +void record(operation value) noexcept { + if (!active) { + return; + } + if (value.communicator != expected_communicator) { + observed.invalid_collective = true; + } + if (observed.operation_count >= + static_cast(observed.operations.size())) { + observed.overflow = true; + return; + } + observed.operations[static_cast(observed.operation_count++)] = + value; +} + +class activation final { + public: + explicit activation(MPI_Comm communicator = MPI_COMM_WORLD) noexcept { + reset(); + expected_communicator = communicator; + active = true; + } + ~activation() noexcept { active = false; } + + activation(activation const&) = delete; + auto operator=(activation const&) -> activation& = delete; +}; +} // namespace evolutionary_probe + +static_assert(noexcept(evolutionary_probe::reset())); +static_assert(noexcept(evolutionary_probe::record({}))); + +extern "C" int MPI_Comm_rank(MPI_Comm communicator, int* rank) { + if (evolutionary_probe::active) { + ++evolutionary_probe::observed.rank_queries; + if (communicator != evolutionary_probe::expected_communicator || + rank == nullptr) { + evolutionary_probe::observed.invalid_collective = true; + } + } + return PMPI_Comm_rank(communicator, rank); +} + +extern "C" int MPI_Comm_size(MPI_Comm communicator, int* size) { + if (evolutionary_probe::active) { + ++evolutionary_probe::observed.size_queries; + if (communicator != evolutionary_probe::expected_communicator || + size == nullptr) { + evolutionary_probe::observed.invalid_collective = true; + } + } + return PMPI_Comm_size(communicator, size); +} + +extern "C" int MPI_Allreduce(void const* send_buffer, + void* receive_buffer, + int count, + MPI_Datatype datatype, + MPI_Op reduction, + MPI_Comm communicator) { + evolutionary_probe::record( + {.kind = evolutionary_probe::operation_kind::all_reduce, + .count = count, + .datatype = datatype, + .reduction = reduction, + .root = -1, + .communicator = communicator}); + return PMPI_Allreduce(send_buffer, receive_buffer, count, datatype, reduction, + communicator); +} + +extern "C" int MPI_Bcast(void* buffer, + int count, + MPI_Datatype datatype, + int root, + MPI_Comm communicator) { + evolutionary_probe::record( + {.kind = evolutionary_probe::operation_kind::broadcast, + .count = count, + .datatype = datatype, + .reduction = MPI_OP_NULL, + .root = root, + .communicator = communicator}); + return PMPI_Bcast(buffer, count, datatype, root, communicator); +} + +#if KAHIP_HAVE_MPI_BCAST_C +extern "C" int MPI_Bcast_c(void* buffer, + MPI_Count count, + MPI_Datatype datatype, + int root, + MPI_Comm communicator) { + evolutionary_probe::record( + {.kind = evolutionary_probe::operation_kind::broadcast, + .count = count, + .datatype = datatype, + .reduction = MPI_OP_NULL, + .root = root, + .communicator = communicator}); + if (evolutionary_probe::active && + evolutionary_probe::suppress_large_count_payload) { + return MPI_SUCCESS; + } + return PMPI_Bcast_c(buffer, count, datatype, root, communicator); +} +#endif + +extern "C" int MPI_Send(void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator) { + if (evolutionary_probe::active) { + evolutionary_probe::observed.forbidden_communication = true; + return MPI_ERR_OTHER; + } + return PMPI_Send(buffer, count, datatype, destination, tag, communicator); +} + +extern "C" int MPI_Isend(void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator, + MPI_Request* request) { + if (request != nullptr) { + *request = MPI_REQUEST_NULL; + } + if (evolutionary_probe::active) { + evolutionary_probe::observed.forbidden_communication = true; + return MPI_ERR_OTHER; + } + return PMPI_Isend(buffer, count, datatype, destination, tag, communicator, + request); +} + +extern "C" int MPI_Recv(void* buffer, + int count, + MPI_Datatype datatype, + int source, + int tag, + MPI_Comm communicator, + MPI_Status* status) { + if (evolutionary_probe::active) { + evolutionary_probe::observed.forbidden_communication = true; + return MPI_ERR_OTHER; + } + return PMPI_Recv(buffer, count, datatype, source, tag, communicator, status); +} + +extern "C" int MPI_Irecv(void* buffer, + int count, + MPI_Datatype datatype, + int source, + int tag, + MPI_Comm communicator, + MPI_Request* request) { + if (request != nullptr) { + *request = MPI_REQUEST_NULL; + } + if (evolutionary_probe::active) { + evolutionary_probe::observed.forbidden_communication = true; + return MPI_ERR_OTHER; + } + return PMPI_Irecv(buffer, count, datatype, source, tag, communicator, + request); +} + +extern "C" int MPI_Sendrecv(void const* send_buffer, + int send_count, + MPI_Datatype send_datatype, + int destination, + int send_tag, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_datatype, + int source, + int receive_tag, + MPI_Comm communicator, + MPI_Status* status) { + if (evolutionary_probe::active) { + evolutionary_probe::observed.forbidden_communication = true; + return MPI_ERR_OTHER; + } + return PMPI_Sendrecv(send_buffer, send_count, send_datatype, destination, + send_tag, receive_buffer, receive_count, + receive_datatype, source, receive_tag, communicator, + status); +} + +extern "C" int MPI_Barrier(MPI_Comm communicator) { + if (evolutionary_probe::active) { + evolutionary_probe::observed.forbidden_communication = true; + return MPI_ERR_OTHER; + } + return PMPI_Barrier(communicator); +} +// KAHIP_PMPI_CALLBACK_REGION_END + +namespace { +using objective_type = std::int64_t; +using weight_type = unsigned; +using partition_type = unsigned; + +[[nodiscard]] auto objective_datatype() noexcept -> MPI_Datatype { + if constexpr (std::same_as) { + return MPI_LONG; + } else if constexpr (std::same_as) { + return MPI_LONG_LONG_INT; + } else { + static_assert(std::same_as || + std::same_as); + } +} + +void require_reduction(evolutionary_probe::operation const& operation, + MPI_Datatype datatype) { + REQUIRE(operation.kind == evolutionary_probe::operation_kind::all_reduce); + REQUIRE(operation.count == 1); + REQUIRE(operation.datatype == datatype); + REQUIRE(operation.reduction == MPI_MIN); + REQUIRE(operation.communicator == MPI_COMM_WORLD); +} + +void require_broadcast(evolutionary_probe::operation const& operation, + MPI_Count count, + int root) { + REQUIRE(operation.kind == evolutionary_probe::operation_kind::broadcast); + REQUIRE(operation.count == count); + REQUIRE(operation.datatype == MPI_UNSIGNED); + REQUIRE(operation.root == root); + REQUIRE(operation.communicator == MPI_COMM_WORLD); +} + +void require_signature_reduction(evolutionary_probe::operation const& operation, + MPI_Op reduction) { + REQUIRE(operation.kind == evolutionary_probe::operation_kind::all_reduce); + REQUIRE(operation.count == 4); + REQUIRE(operation.datatype == MPI_UINT64_T); + REQUIRE(operation.reduction == reduction); + REQUIRE(operation.communicator == MPI_COMM_WORLD); +} + +void require_clean_probe(evolutionary_probe::counters const& observed) { + REQUIRE_FALSE(observed.overflow); + REQUIRE_FALSE(observed.invalid_collective); + REQUIRE_FALSE(observed.forbidden_communication); +} +} // namespace + +TEST_CASE("unsigned evolutionary permutation uses MPI_UNSIGNED") { + auto rank = -1; + auto size = 0; + REQUIRE(PMPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(PMPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + + auto permutation = std::vector(static_cast(size), 0); + if (rank == 0) { + for (auto index = 0; index < size; ++index) { + permutation[static_cast(index)] = + static_cast(size - index - 1); + } + } + + evolutionary_probe::counters observed{}; + { + evolutionary_probe::activation const probe; + kahip::parallel_mh::broadcast_permutation(MPI_COMM_WORLD, permutation, 0); + observed = evolutionary_probe::observed; + } + + REQUIRE(observed.operation_count == 1); + require_clean_probe(observed); + REQUIRE(observed.rank_queries == 0); + REQUIRE(observed.size_queries == 0); + require_broadcast(observed.operations[0], size, 0); + for (auto index = 0; index < size; ++index) { + REQUIRE(permutation[static_cast(index)] == + static_cast(size - index - 1)); + } +} + +TEST_CASE( + "best feasible partition retains wide scalar domains and exact winner") { + auto rank = -1; + auto size = 0; + REQUIRE(PMPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(PMPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + + constexpr auto objective = + static_cast(std::numeric_limits::max()) + 4096; + constexpr auto weight = + static_cast(std::numeric_limits::max()) + 1024U; + constexpr auto partition = + static_cast(std::numeric_limits::max()) + 2048U; + auto const winner = size - 1; + auto const local_objective = + rank == 0 || rank == winner ? objective : objective + 19; + auto const local_weight = rank == winner ? weight : weight + 7U; + auto local_map = + std::array{partition + static_cast(rank * 8), + partition + static_cast(rank * 8 + 1), + partition + static_cast(rank * 8 + 2)}; + + objective_type actual_objective = 0; + evolutionary_probe::counters observed{}; + { + evolutionary_probe::activation const probe; + actual_objective = kahip::parallel_mh::select_and_broadcast_best_partition( + MPI_COMM_WORLD, local_objective, local_weight, + std::numeric_limits::max(), local_map.data(), + local_map.size()); + observed = evolutionary_probe::observed; + } + + REQUIRE(actual_objective == objective); + REQUIRE(local_map == + std::array{partition + static_cast(winner * 8), + partition + static_cast(winner * 8 + 1), + partition + static_cast(winner * 8 + 2)}); + REQUIRE(observed.rank_queries == 1); + REQUIRE(observed.size_queries == 1); + REQUIRE(observed.operation_count == 8); + require_clean_probe(observed); + require_reduction(observed.operations[0], MPI_INT); + require_reduction(observed.operations[1], objective_datatype()); + require_reduction(observed.operations[2], MPI_UNSIGNED); + require_reduction(observed.operations[3], MPI_INT); + require_signature_reduction(observed.operations[4], MPI_MIN); + require_signature_reduction(observed.operations[5], MPI_MAX); + require_reduction(observed.operations[6], MPI_INT); + require_broadcast(observed.operations[7], 3, winner); +} + +TEST_CASE( + "all-infeasible objective fallback breaks an exact tie by lowest rank") { + auto rank = -1; + REQUIRE(PMPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + + constexpr auto objective = + static_cast(std::numeric_limits::max()) + 8192; + constexpr auto weight = + static_cast(std::numeric_limits::max()) + 4096U; + constexpr auto partition = + static_cast(std::numeric_limits::max()) + 8192U; + auto local_map = + std::array{partition + static_cast(rank * 4), + partition + static_cast(rank * 4 + 1)}; + + objective_type actual_objective = 0; + evolutionary_probe::counters observed{}; + { + evolutionary_probe::activation const probe; + actual_objective = kahip::parallel_mh::select_and_broadcast_best_partition( + MPI_COMM_WORLD, objective, weight, weight - 1U, local_map.data(), + local_map.size()); + observed = evolutionary_probe::observed; + } + + REQUIRE(actual_objective == objective); + REQUIRE(local_map == std::array{partition, partition + 1U}); + REQUIRE(observed.rank_queries == 1); + REQUIRE(observed.size_queries == 1); + REQUIRE(observed.operation_count == 8); + require_clean_probe(observed); + require_reduction(observed.operations[0], MPI_INT); + require_reduction(observed.operations[1], objective_datatype()); + require_reduction(observed.operations[2], MPI_UNSIGNED); + require_reduction(observed.operations[3], MPI_INT); + require_signature_reduction(observed.operations[4], MPI_MIN); + require_signature_reduction(observed.operations[5], MPI_MAX); + require_reduction(observed.operations[6], MPI_INT); + require_broadcast(observed.operations[7], 2, 0); +} + +TEST_CASE("maximum feasible objective is not confused with infeasibility") { + auto rank = -1; + auto size = 0; + REQUIRE(PMPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(PMPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + + constexpr auto maximum_objective = std::numeric_limits::max(); + constexpr weight_type upper_bound = 64; + constexpr partition_type partition = 8192; + auto const local_objective = + rank == 0 ? maximum_objective : objective_type{1}; + auto const local_weight = + rank == 0 || size == 1 ? upper_bound : upper_bound + 1; + auto local_map = + std::array{partition + static_cast(rank * 2), + partition + static_cast(rank * 2 + 1)}; + + objective_type actual_objective = 0; + evolutionary_probe::counters observed{}; + { + evolutionary_probe::activation const probe; + actual_objective = kahip::parallel_mh::select_and_broadcast_best_partition( + MPI_COMM_WORLD, local_objective, local_weight, upper_bound, + local_map.data(), local_map.size()); + observed = evolutionary_probe::observed; + } + + REQUIRE(actual_objective == maximum_objective); + REQUIRE(local_map == std::array{partition, partition + 1U}); + REQUIRE(observed.rank_queries == 1); + REQUIRE(observed.size_queries == 1); + REQUIRE(observed.operation_count == 8); + require_clean_probe(observed); + require_reduction(observed.operations[0], MPI_INT); + require_reduction(observed.operations[1], objective_datatype()); + require_reduction(observed.operations[2], MPI_UNSIGNED); + require_reduction(observed.operations[3], MPI_INT); + require_signature_reduction(observed.operations[4], MPI_MIN); + require_signature_reduction(observed.operations[5], MPI_MAX); + require_reduction(observed.operations[6], MPI_INT); + require_broadcast(observed.operations[7], 2, 0); +} + +TEST_CASE( + "forced MPI-3 partition broadcast uses deterministic bounded rounds") { + auto rank = -1; + REQUIRE(PMPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + + auto local_map = std::array{ + static_cast(rank * 8), static_cast(rank * 8 + 1), + static_cast(rank * 8 + 2), static_cast(rank * 8 + 3), + static_cast(rank * 8 + 4)}; + + evolutionary_probe::counters observed{}; + { + evolutionary_probe::activation const probe; + static_cast(kahip::parallel_mh::select_and_broadcast_best_partition( + MPI_COMM_WORLD, std::int64_t{17 + rank}, unsigned{32}, unsigned{64}, + local_map.data(), local_map.size(), + {.mpi3_round_ceiling = 2, .force_mpi3 = true})); + observed = evolutionary_probe::observed; + } + + require_clean_probe(observed); + REQUIRE(observed.rank_queries == 1); + REQUIRE(observed.size_queries == 1); + REQUIRE(local_map == std::array{0, 1, 2, 3, 4}); + auto broadcast_counts = std::vector{}; + for (auto index = 0; index < observed.operation_count; ++index) { + auto const& operation = + observed.operations[static_cast(index)]; + if (operation.kind == evolutionary_probe::operation_kind::broadcast) { + broadcast_counts.push_back(operation.count); + REQUIRE(operation.datatype == MPI_UNSIGNED); + REQUIRE(operation.root == 0); + REQUIRE(operation.communicator == MPI_COMM_WORLD); + } + } + REQUIRE(broadcast_counts == std::vector{2, 2, 1}); +} + +TEST_CASE("empty partition payload is a valid collective no-op") { + auto rank = -1; + REQUIRE(PMPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + + evolutionary_probe::counters observed{}; + auto best_objective = objective_type{}; + { + evolutionary_probe::activation const probe; + best_objective = kahip::parallel_mh::select_and_broadcast_best_partition( + MPI_COMM_WORLD, objective_type{23 + rank}, unsigned{1}, unsigned{2}, + static_cast(nullptr), 0); + observed = evolutionary_probe::observed; + } + + require_clean_probe(observed); + REQUIRE(observed.rank_queries == 1); + REQUIRE(observed.size_queries == 1); + REQUIRE(best_objective == objective_type{23}); + REQUIRE(observed.operation_count == 7); + for (auto index = 0; index < observed.operation_count; ++index) { + REQUIRE(observed.operations[static_cast(index)].kind == + evolutionary_probe::operation_kind::all_reduce); + } +} + +#if KAHIP_HAVE_MPI_BCAST_C +TEST_CASE("MPI-4 partition broadcast preserves a count above INT_MAX") { + auto rank = -1; + REQUIRE(PMPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + + auto partition = static_cast(rank); + constexpr auto large_count = + static_cast(std::numeric_limits::max()) + 1U; + evolutionary_probe::counters observed{}; + { + evolutionary_probe::activation const probe; + evolutionary_probe::suppress_large_count_payload = true; + static_cast(kahip::parallel_mh::select_and_broadcast_best_partition( + MPI_COMM_WORLD, std::int64_t{7 + rank}, unsigned{1}, unsigned{2}, + &partition, large_count)); + observed = evolutionary_probe::observed; + } + + require_clean_probe(observed); + REQUIRE(observed.rank_queries == 1); + REQUIRE(observed.size_queries == 1); + REQUIRE(observed.operation_count == 8); + require_broadcast(observed.operations[7], static_cast(large_count), + 0); +} +#endif diff --git a/parallel/parallel_src/tests/communication/mpi_adapter_test.cpp b/parallel/parallel_src/tests/communication/mpi_adapter_test.cpp new file mode 100644 index 00000000..17eef328 --- /dev/null +++ b/parallel/parallel_src/tests/communication/mpi_adapter_test.cpp @@ -0,0 +1,1745 @@ +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "communication/contiguous_owner_layout.h" +#include "communication/mpi_adapter.h" +#include "communication/mpi_failure.h" +#include "kahip_mpi_capabilities.h" +#include "parhip_interface.h" + +namespace test_support { +struct wire_entry { + std::uint64_t id; + int owner; + double weight; + + auto operator==(wire_entry const&) const -> bool = default; +}; + +struct non_default_wire_entry { + non_default_wire_entry() = delete; + constexpr non_default_wire_entry(std::uint64_t entry_id, + int entry_owner) noexcept + : id(entry_id), owner(entry_owner) {} + + std::uint64_t id; + int owner; + + auto operator==(non_default_wire_entry const&) const -> bool = default; +}; + +struct unsupported_wire_entry { + std::array values; +}; +} // namespace test_support + +template <> +struct parhip::mpi::wire_members { + inline static constexpr auto value = std::tuple{ + &test_support::wire_entry::id, + &test_support::wire_entry::owner, + &test_support::wire_entry::weight}; +}; + +template <> +struct parhip::mpi::wire_members { + inline static constexpr auto value = std::tuple{ + &test_support::non_default_wire_entry::id, + &test_support::non_default_wire_entry::owner}; +}; + +template <> +struct parhip::mpi::wire_members { + inline static constexpr auto value = + std::tuple{&test_support::unsupported_wire_entry::values}; +}; + +namespace semantic_error_protocol_probe { +inline bool active = false; +inline int error_string_calls = 0; + +class activation final { + public: + activation() noexcept { + error_string_calls = 0; + active = true; + } + ~activation() noexcept { active = false; } + + activation(activation const&) = delete; + auto operator=(activation const&) -> activation& = delete; +}; +} // namespace semantic_error_protocol_probe + +namespace capacity_protocol_probe { +inline bool active = false; +inline int allreduce_calls = 0; +inline bool signature_matches = true; + +class activation final { + public: + activation() noexcept { + allreduce_calls = 0; + signature_matches = true; + active = true; + } + ~activation() noexcept { active = false; } + + activation(activation const&) = delete; + auto operator=(activation const&) -> activation& = delete; +}; +} // namespace capacity_protocol_probe + +extern "C" int MPI_Error_string(int error_code, + char* error_text, + int* error_text_length) { + if (semantic_error_protocol_probe::active) { + ++semantic_error_protocol_probe::error_string_calls; + } + return PMPI_Error_string(error_code, error_text, error_text_length); +} + +extern "C" int MPI_Allreduce(void const* send_buffer, + void* receive_buffer, + int count, + MPI_Datatype datatype, + MPI_Op operation, + MPI_Comm communicator) { + if (capacity_protocol_probe::active) { + ++capacity_protocol_probe::allreduce_calls; + capacity_protocol_probe::signature_matches = + capacity_protocol_probe::signature_matches && send_buffer != nullptr && + receive_buffer != nullptr && count == 2 && datatype == MPI_UINT64_T && + operation == MPI_BOR; + } + return PMPI_Allreduce(send_buffer, receive_buffer, count, datatype, operation, + communicator); +} + +namespace neighborhood_protocol_probe { +inline bool active = false; +inline int dist_graph_create_calls = 0; +inline int neighbor_count_calls = 0; +inline int neighbor_payload_calls = 0; +inline int neighbor_payload_c_calls = 0; +inline int point_to_point_calls = 0; +inline int maximum_active_send_segments = 0; +inline int maximum_active_receive_segments = 0; +inline int maximum_payload_count = 0; +inline int nonzero_displacement_calls = 0; + +void reset() { + dist_graph_create_calls = 0; + neighbor_count_calls = 0; + neighbor_payload_calls = 0; + neighbor_payload_c_calls = 0; + point_to_point_calls = 0; + maximum_active_send_segments = 0; + maximum_active_receive_segments = 0; + maximum_payload_count = 0; + nonzero_displacement_calls = 0; +} +} // namespace neighborhood_protocol_probe + +extern "C" int MPI_Dist_graph_create(MPI_Comm communicator, + int source_count, + int const sources[], + int const degrees[], + int const destinations[], + int const weights[], + MPI_Info info, + int reorder, + MPI_Comm* graph_communicator) { + if (neighborhood_protocol_probe::active) { + ++neighborhood_protocol_probe::dist_graph_create_calls; + } + return PMPI_Dist_graph_create(communicator, source_count, sources, degrees, + destinations, weights, info, reorder, + graph_communicator); +} + +extern "C" int MPI_Neighbor_alltoall(void const* send_buffer, + int send_count, + MPI_Datatype send_datatype, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (neighborhood_protocol_probe::active) { + ++neighborhood_protocol_probe::neighbor_count_calls; + } + return PMPI_Neighbor_alltoall(send_buffer, send_count, send_datatype, + receive_buffer, receive_count, receive_datatype, + communicator); +} + +extern "C" int MPI_Neighbor_alltoallv(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (neighborhood_protocol_probe::active) { + ++neighborhood_protocol_probe::neighbor_payload_calls; + auto indegree = 0; + auto outdegree = 0; + auto weighted = 0; + if (PMPI_Dist_graph_neighbors_count(communicator, &indegree, &outdegree, + &weighted) == MPI_SUCCESS) { + auto active_sends = 0; + auto active_receives = 0; + for (int index = 0; index < outdegree; ++index) { + active_sends += send_counts[index] != 0 ? 1 : 0; + neighborhood_protocol_probe::maximum_payload_count = + std::max(neighborhood_protocol_probe::maximum_payload_count, + send_counts[index]); + neighborhood_protocol_probe::nonzero_displacement_calls += + send_displacements[index] != 0 ? 1 : 0; + } + for (int index = 0; index < indegree; ++index) { + active_receives += receive_counts[index] != 0 ? 1 : 0; + neighborhood_protocol_probe::maximum_payload_count = + std::max(neighborhood_protocol_probe::maximum_payload_count, + receive_counts[index]); + neighborhood_protocol_probe::nonzero_displacement_calls += + receive_displacements[index] != 0 ? 1 : 0; + } + neighborhood_protocol_probe::maximum_active_send_segments = + std::max(neighborhood_protocol_probe::maximum_active_send_segments, + active_sends); + neighborhood_protocol_probe::maximum_active_receive_segments = + std::max(neighborhood_protocol_probe::maximum_active_receive_segments, + active_receives); + } + } + return PMPI_Neighbor_alltoallv(send_buffer, send_counts, send_displacements, + send_datatype, receive_buffer, receive_counts, + receive_displacements, receive_datatype, + communicator); +} + +#if defined(KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C) && \ + KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C +extern "C" int MPI_Neighbor_alltoallv_c(void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (neighborhood_protocol_probe::active) { + ++neighborhood_protocol_probe::neighbor_payload_c_calls; + } + return PMPI_Neighbor_alltoallv_c(send_buffer, send_counts, send_displacements, + send_datatype, receive_buffer, + receive_counts, receive_displacements, + receive_datatype, communicator); +} +#endif + +extern "C" int MPI_Isend(void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator, + MPI_Request* request) { + if (neighborhood_protocol_probe::active) { + ++neighborhood_protocol_probe::point_to_point_calls; + } + return PMPI_Isend(buffer, count, datatype, destination, tag, communicator, + request); +} + +extern "C" int MPI_Irecv(void* buffer, + int count, + MPI_Datatype datatype, + int source, + int tag, + MPI_Comm communicator, + MPI_Request* request) { + if (neighborhood_protocol_probe::active) { + ++neighborhood_protocol_probe::point_to_point_calls; + } + return PMPI_Irecv(buffer, count, datatype, source, tag, communicator, + request); +} + +extern "C" int MPI_Recv(void* buffer, + int count, + MPI_Datatype datatype, + int source, + int tag, + MPI_Comm communicator, + MPI_Status* status) { + if (neighborhood_protocol_probe::active) { + ++neighborhood_protocol_probe::point_to_point_calls; + } + return PMPI_Recv(buffer, count, datatype, source, tag, communicator, status); +} + +extern "C" int MPI_Probe(int source, + int tag, + MPI_Comm communicator, + MPI_Status* status) { + if (neighborhood_protocol_probe::active) { + ++neighborhood_protocol_probe::point_to_point_calls; + } + return PMPI_Probe(source, tag, communicator, status); +} + +extern "C" int MPI_Iprobe(int source, + int tag, + MPI_Comm communicator, + int* flag, + MPI_Status* status) { + if (neighborhood_protocol_probe::active) { + ++neighborhood_protocol_probe::point_to_point_calls; + } + return PMPI_Iprobe(source, tag, communicator, flag, status); +} + +extern "C" int MPI_Mprobe(int source, + int tag, + MPI_Comm communicator, + MPI_Message* message, + MPI_Status* status) { + if (neighborhood_protocol_probe::active) { + ++neighborhood_protocol_probe::point_to_point_calls; + } + return PMPI_Mprobe(source, tag, communicator, message, status); +} + +extern "C" int MPI_Improbe(int source, + int tag, + MPI_Comm communicator, + int* flag, + MPI_Message* message, + MPI_Status* status) { + if (neighborhood_protocol_probe::active) { + ++neighborhood_protocol_probe::point_to_point_calls; + } + return PMPI_Improbe(source, tag, communicator, flag, message, status); +} + +extern "C" int MPI_Send(void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator) { + if (neighborhood_protocol_probe::active) { + ++neighborhood_protocol_probe::point_to_point_calls; + } + return PMPI_Send(buffer, count, datatype, destination, tag, communicator); +} + +extern "C" int MPI_Sendrecv(void const* send_buffer, + int send_count, + MPI_Datatype send_datatype, + int destination, + int send_tag, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_datatype, + int source, + int receive_tag, + MPI_Comm communicator, + MPI_Status* status) { + if (neighborhood_protocol_probe::active) { + ++neighborhood_protocol_probe::point_to_point_calls; + } + return PMPI_Sendrecv(send_buffer, send_count, send_datatype, destination, + send_tag, receive_buffer, receive_count, + receive_datatype, source, receive_tag, communicator, + status); +} + +extern "C" int MPI_Sendrecv_replace(void* buffer, + int count, + MPI_Datatype datatype, + int destination, + int send_tag, + int source, + int receive_tag, + MPI_Comm communicator, + MPI_Status* status) { + if (neighborhood_protocol_probe::active) { + ++neighborhood_protocol_probe::point_to_point_calls; + } + return PMPI_Sendrecv_replace(buffer, count, datatype, destination, send_tag, + source, receive_tag, communicator, status); +} + +extern "C" int MPI_Wait(MPI_Request* request, MPI_Status* status) { + if (neighborhood_protocol_probe::active) { + ++neighborhood_protocol_probe::point_to_point_calls; + } + return PMPI_Wait(request, status); +} + +extern "C" int MPI_Waitall(int count, + MPI_Request requests[], + MPI_Status statuses[]) { + if (neighborhood_protocol_probe::active) { + ++neighborhood_protocol_probe::point_to_point_calls; + } + return PMPI_Waitall(count, requests, statuses); +} + +namespace { +using parhip::mpi::agree_collectively; +using parhip::mpi::all_to_all_v; +using parhip::mpi::capacity_issue; +using parhip::mpi::capacity_issue_diagnostic; +using parhip::mpi::capacity_issue_mask; +using parhip::mpi::capacity_result; +using parhip::mpi::capacity_route; +using parhip::mpi::capacity_route_for; +using parhip::mpi::collective_options; +using parhip::mpi::communicator; +using parhip::mpi::communicator_view; +using parhip::mpi::contiguous_owner_layout; +using parhip::mpi::distributed_graph; +using parhip::mpi::first_fatal_capacity_issue; +using parhip::mpi::has_bounded_capacity_issue; +using parhip::mpi::has_fatal_capacity_issue; +using parhip::mpi::make_mpi_datatype; +using parhip::mpi::neighbor_all_to_all_v; +using parhip::mpi::resolve_capacity_collectively; +using parhip::mpi::run_with_exception_barrier; +using parhip::mpi::runtime_is_active; +using parhip::mpi::segmented_buffer; +using parhip::mpi::topology; +using parhip::mpi::validate_collectively; +using parhip::mpi::with_bounded_capacity_issue; +using parhip::mpi::with_fatal_capacity_issue; + +template +void require_exact_common_mpi_error(Operation&& operation, + std::string_view expected_context, + communicator_view communicator) { + auto caught = 0; + auto exact_dynamic_type = 0; + auto raw_code_matches = 0; + auto context_matches = 0; + auto error_string_calls = 0; + { + semantic_error_protocol_probe::activation observation{}; + try { + std::invoke(std::forward(operation)); + } catch (parhip::mpi::mpi_error const& error) { + caught = 1; + exact_dynamic_type = + typeid(error) == typeid(parhip::mpi::mpi_error) ? 1 : 0; + raw_code_matches = error.error_code() == MPI_ERR_ARG ? 1 : 0; + context_matches = error.context() == expected_context ? 1 : 0; + } catch (...) { + caught = 1; + } + error_string_calls = semantic_error_protocol_probe::error_string_calls; + } + + auto totals = std::array{caught, exact_dynamic_type, raw_code_matches, + context_matches, error_string_calls}; + auto global_totals = std::array{0, 0, 0, 0, 0}; + REQUIRE(PMPI_Allreduce(totals.data(), global_totals.data(), + static_cast(totals.size()), MPI_INT, MPI_SUM, + communicator.native_handle()) == MPI_SUCCESS); + auto const size = communicator.size(); + REQUIRE(global_totals == std::array{size, size, size, size, 0}); +} + +template +void require_collective_semantic_error(Operation&& operation, + std::string_view expected_context, + communicator_view communicator) { + require_exact_common_mpi_error(std::forward(operation), + expected_context, communicator); +} + +TEST_CASE("capacity issues have stable bits diagnostics and fatal priority", + "[unit][mpi][failure-policy][capacity][pure]") { + STATIC_REQUIRE(std::is_trivially_copyable_v); + STATIC_REQUIRE(std::is_standard_layout_v); + STATIC_REQUIRE(sizeof(capacity_result) == 2 * sizeof(std::uint64_t)); + + STATIC_REQUIRE( + capacity_issue_mask(capacity_issue::received_count_not_representable) == + (std::uint64_t{1} << 0)); + STATIC_REQUIRE( + capacity_issue_mask(capacity_issue::cumulative_offset_overflow) == + (std::uint64_t{1} << 1)); + STATIC_REQUIRE( + capacity_issue_mask(capacity_issue::storage_byte_size_overflow) == + (std::uint64_t{1} << 2)); + STATIC_REQUIRE( + capacity_issue_mask(capacity_issue::topology_degree_not_representable) == + (std::uint64_t{1} << 3)); + STATIC_REQUIRE(capacity_issue_mask( + capacity_issue::collective_layout_not_representable) == + (std::uint64_t{1} << 4)); + STATIC_REQUIRE( + capacity_issue_mask(capacity_issue::direct_backend_not_representable) == + (std::uint64_t{1} << 5)); + STATIC_REQUIRE( + capacity_issue_mask(capacity_issue::bounded_round_arithmetic_overflow) == + (std::uint64_t{1} << 6)); + + STATIC_REQUIRE(capacity_issue_diagnostic( + capacity_issue::received_count_not_representable) == + "received element count exceeds local size_t capacity"); + STATIC_REQUIRE( + capacity_issue_diagnostic(capacity_issue::cumulative_offset_overflow) == + "cumulative element offset exceeds local size_t capacity"); + STATIC_REQUIRE( + capacity_issue_diagnostic(capacity_issue::storage_byte_size_overflow) == + "element storage byte size exceeds local size_t capacity"); + STATIC_REQUIRE(capacity_issue_diagnostic( + capacity_issue::topology_degree_not_representable) == + "distributed graph outdegree exceeds MPI int capacity"); + STATIC_REQUIRE(capacity_issue_diagnostic( + capacity_issue::collective_layout_not_representable) == + "collective payload layout has no representable MPI backend"); + STATIC_REQUIRE( + capacity_issue_diagnostic( + capacity_issue::direct_backend_not_representable) == + "direct neighborhood payload has no representable MPI backend"); + STATIC_REQUIRE( + capacity_issue_diagnostic( + capacity_issue::bounded_round_arithmetic_overflow) == + "bounded MPI-3 chunk arithmetic exceeds local size_t capacity"); + + constexpr auto empty = capacity_result{}; + constexpr auto fallback = with_bounded_capacity_issue( + empty, capacity_issue::collective_layout_not_representable); + constexpr auto high_fatal = with_fatal_capacity_issue( + fallback, capacity_issue::direct_backend_not_representable); + constexpr auto mixed = with_fatal_capacity_issue( + high_fatal, capacity_issue::received_count_not_representable); + + STATIC_REQUIRE_FALSE(has_fatal_capacity_issue( + empty, capacity_issue::direct_backend_not_representable)); + STATIC_REQUIRE(has_bounded_capacity_issue( + fallback, capacity_issue::collective_layout_not_representable)); + STATIC_REQUIRE(has_fatal_capacity_issue( + mixed, capacity_issue::direct_backend_not_representable)); + STATIC_REQUIRE(first_fatal_capacity_issue(mixed).has_value()); + STATIC_REQUIRE(*first_fatal_capacity_issue(mixed) == + capacity_issue::received_count_not_representable); + STATIC_REQUIRE(capacity_route_for(empty) == capacity_route::direct); + STATIC_REQUIRE(capacity_route_for(fallback) == capacity_route::bounded); + STATIC_REQUIRE_FALSE(capacity_route_for(mixed).has_value()); + STATIC_REQUIRE(noexcept(resolve_capacity_collectively( + capacity_result{}, MPI_COMM_WORLD, MPI_COMM_WORLD, std::string_view{}))); +} + +TEST_CASE("capacity resolver performs one BOR and agrees direct or bounded", + "[unit][mpi][failure-policy][capacity][collective]") { + communicator_view const world{MPI_COMM_WORLD}; + + auto require_route = [&](capacity_result local, capacity_route expected) { + auto route_matches = 0; + auto allreduce_calls = 0; + auto signature_matches = 0; + auto error_string_calls = 0; + { + semantic_error_protocol_probe::activation error_string_observation{}; + capacity_protocol_probe::activation collective_observation{}; + auto const route = resolve_capacity_collectively( + local, world.native_handle(), world.native_handle(), + "capacity route test"); + route_matches = route == expected ? 1 : 0; + allreduce_calls = capacity_protocol_probe::allreduce_calls; + signature_matches = capacity_protocol_probe::signature_matches ? 1 : 0; + error_string_calls = semantic_error_protocol_probe::error_string_calls; + } + + auto local_result = std::array{route_matches, allreduce_calls, + signature_matches, error_string_calls}; + auto global_result = std::array{0, 0, 0, 0}; + REQUIRE(PMPI_Allreduce(local_result.data(), global_result.data(), + static_cast(local_result.size()), MPI_INT, + MPI_SUM, world.native_handle()) == MPI_SUCCESS); + REQUIRE(global_result == + std::array{world.size(), world.size(), world.size(), 0}); + }; + + SECTION("no issue selects the direct route") { + require_route(capacity_result{}, capacity_route::direct); + } + + SECTION("one rank requesting fallback selects bounded everywhere") { + auto local = capacity_result{}; + if (world.rank() == 0) { + local = with_bounded_capacity_issue( + local, capacity_issue::collective_layout_not_representable); + } + require_route(local, capacity_route::bounded); + } +} + +TEST_CASE("dense preflight routes synthetic MPI-4 layout fallback collectively", + "[unit][mpi][failure-policy][capacity][dense]") { + constexpr auto count_failure = with_fatal_capacity_issue( + capacity_result{}, capacity_issue::received_count_not_representable); + constexpr auto offset_failure = with_fatal_capacity_issue( + capacity_result{}, capacity_issue::cumulative_offset_overflow); + constexpr auto all_failures = + parhip::mpi::detail::dense_capacity_preflight( + parhip::mpi::detail::combine_capacity_results(count_failure, + offset_failure), + std::numeric_limits::max(), true, false); + STATIC_REQUIRE(has_fatal_capacity_issue( + all_failures, capacity_issue::received_count_not_representable)); + STATIC_REQUIRE(has_fatal_capacity_issue( + all_failures, capacity_issue::cumulative_offset_overflow)); + STATIC_REQUIRE(has_fatal_capacity_issue( + all_failures, capacity_issue::storage_byte_size_overflow)); + STATIC_REQUIRE(has_bounded_capacity_issue( + all_failures, capacity_issue::collective_layout_not_representable)); + STATIC_REQUIRE(first_fatal_capacity_issue(all_failures) == + capacity_issue::received_count_not_representable); + + communicator_view const world{MPI_COMM_WORLD}; + + auto require_dense_route = [&](bool local_layout_is_representable, + capacity_route expected) { + auto const local = + parhip::mpi::detail::dense_capacity_preflight( + capacity_result{}, std::size_t{0}, true, + local_layout_is_representable); + auto allreduce_calls = 0; + auto signature_matches = 0; + auto error_string_calls = 0; + auto route = capacity_route::direct; + { + semantic_error_protocol_probe::activation error_observation{}; + capacity_protocol_probe::activation collective_observation{}; + route = resolve_capacity_collectively( + local, world.native_handle(), world.native_handle(), + "dense synthetic MPI-4 layout preflight"); + allreduce_calls = capacity_protocol_probe::allreduce_calls; + signature_matches = capacity_protocol_probe::signature_matches ? 1 : 0; + error_string_calls = semantic_error_protocol_probe::error_string_calls; + } + + auto const local_result = + std::array{route == expected ? 1 : 0, allreduce_calls, + signature_matches, error_string_calls}; + auto global_result = std::array{0, 0, 0, 0}; + REQUIRE(PMPI_Allreduce(local_result.data(), global_result.data(), + static_cast(local_result.size()), MPI_INT, + MPI_SUM, world.native_handle()) == MPI_SUCCESS); + REQUIRE(global_result == + std::array{world.size(), world.size(), world.size(), 0}); + }; + + SECTION("representable layout remains direct") { + require_dense_route(true, capacity_route::direct); + } + + SECTION("one unrepresentable MPI-4 layout selects bounded") { + require_dense_route(world.rank() != 0, capacity_route::bounded); + } +} + +TEST_CASE("contiguous ownership uses exact integer boundaries", + "[unit][mpi][ownership]") { + using id_type = std::uint64_t; + + SECTION("zero work has no owners") { + constexpr contiguous_owner_layout layout{0, 5}; + STATIC_REQUIRE(layout.chunk_size() == 1); + STATIC_REQUIRE(layout.boundary(0) == 0); + STATIC_REQUIRE(layout.boundary(5) == 0); + STATIC_REQUIRE_FALSE(layout.owner(0).has_value()); + } + + SECTION("more ranks than IDs leaves trailing ranks empty") { + constexpr contiguous_owner_layout layout{2, 5}; + STATIC_REQUIRE(layout.chunk_size() == 1); + STATIC_REQUIRE(layout.begin(0) == 0); + STATIC_REQUIRE(layout.end(0) == 1); + STATIC_REQUIRE(layout.begin(1) == 1); + STATIC_REQUIRE(layout.end(1) == 2); + STATIC_REQUIRE(layout.begin(2) == 2); + STATIC_REQUIRE(layout.end(4) == 2); + STATIC_REQUIRE(layout.owner(0) == 0); + STATIC_REQUIRE(layout.owner(1) == 1); + STATIC_REQUIRE_FALSE(layout.owner(2).has_value()); + } + + SECTION("uneven ownership retains the pinned fixed-chunk partition") { + constexpr contiguous_owner_layout layout{4, 3}; + STATIC_REQUIRE(layout.chunk_size() == 2); + STATIC_REQUIRE(layout.boundary(0) == 0); + STATIC_REQUIRE(layout.boundary(1) == 2); + STATIC_REQUIRE(layout.boundary(2) == 4); + STATIC_REQUIRE(layout.boundary(3) == 4); + STATIC_REQUIRE(layout.owner(0) == 0); + STATIC_REQUIRE(layout.owner(1) == 0); + STATIC_REQUIRE(layout.owner(2) == 1); + STATIC_REQUIRE(layout.owner(3) == 1); + } + + SECTION("values above the exact double integer range stay exact") { + constexpr auto total = (id_type{1} << 53) + 1; + constexpr contiguous_owner_layout layout{total, 2}; + STATIC_REQUIRE(layout.chunk_size() == (id_type{1} << 52) + 1); + STATIC_REQUIRE(layout.boundary(1) == (id_type{1} << 52) + 1); + STATIC_REQUIRE(layout.boundary(2) == total); + STATIC_REQUIRE(layout.owner(total - 1) == 1); + } + + SECTION("maximum NodeID never overflows a boundary product") { + constexpr auto total = std::numeric_limits::max(); + constexpr contiguous_owner_layout two_ranks{total, 2}; + constexpr contiguous_owner_layout three_ranks{total, 3}; + constexpr contiguous_owner_layout five_ranks{total, 5}; + STATIC_REQUIRE(two_ranks.boundary(2) == total); + STATIC_REQUIRE(three_ranks.boundary(3) == total); + STATIC_REQUIRE(five_ranks.boundary(5) == total); + STATIC_REQUIRE(two_ranks.owner(total - 1) == 1); + STATIC_REQUIRE(three_ranks.owner(total - 1) == 2); + STATIC_REQUIRE(five_ranks.owner(total - 1) == 4); + } +} + +TEST_CASE("native MPI types use the closed tuple mapping", "[unit][mpi]") { + using native_types = parhip::mpi::detail::native_mpi_types; + STATIC_REQUIRE( + parhip::mpi::detail::tuple_contains_v); + STATIC_REQUIRE_FALSE( + parhip::mpi::detail::tuple_contains_v); + STATIC_REQUIRE(parhip::mpi::detail::tuple_index_v == 0); + STATIC_REQUIRE(parhip::mpi::detail::tuple_index_v == 6); + STATIC_REQUIRE( + parhip::mpi::detail::tuple_index_v == 13); + STATIC_REQUIRE( + std::tuple_size_v == + std::tuple_size_v>); + + REQUIRE(parhip::mpi::detail::native_mpi_handles == + std::array{MPI_CHAR, + MPI_WCHAR, + MPI_SIGNED_CHAR, + MPI_UNSIGNED_CHAR, + MPI_SHORT, + MPI_UNSIGNED_SHORT, + MPI_INT, + MPI_UNSIGNED, + MPI_LONG, + MPI_UNSIGNED_LONG, + MPI_LONG_LONG_INT, + MPI_UNSIGNED_LONG_LONG, + MPI_FLOAT, + MPI_DOUBLE, + MPI_LONG_DOUBLE, + MPI_CXX_BOOL, + MPI_CXX_FLOAT_COMPLEX, + MPI_CXX_DOUBLE_COMPLEX, + MPI_CXX_LONG_DOUBLE_COMPLEX}); + + STATIC_REQUIRE(parhip::mpi::mpi_native_datatype); + STATIC_REQUIRE(parhip::mpi::mpi_native_datatype); + STATIC_REQUIRE(parhip::mpi::mpi_native_datatype); + STATIC_REQUIRE_FALSE(parhip::mpi::mpi_native_datatype); + + REQUIRE(make_mpi_datatype().native_handle() == MPI_INT); + REQUIRE(make_mpi_datatype().native_handle() == + MPI_UNSIGNED_LONG); + REQUIRE(make_mpi_datatype().native_handle() == MPI_DOUBLE); + REQUIRE_FALSE(make_mpi_datatype().owns_handle()); +} + +TEST_CASE("wire metadata rejects members without native MPI handles", + "[unit][mpi]") { + STATIC_REQUIRE( + std::is_standard_layout_v); + STATIC_REQUIRE( + std::is_trivially_copyable_v); + STATIC_REQUIRE_FALSE( + parhip::mpi::mpi_wire_datatype); + STATIC_REQUIRE_FALSE( + parhip::mpi::mpi_datatype); +} + +TEST_CASE("communicator and topology ownership stays scoped", "[unit][mpi]") { + STATIC_REQUIRE_FALSE(std::is_copy_constructible_v); + STATIC_REQUIRE(std::is_move_constructible_v); + STATIC_REQUIRE_FALSE(std::is_copy_constructible_v); + STATIC_REQUIRE(std::is_move_constructible_v); + + communicator_view const world{MPI_COMM_WORLD}; + REQUIRE(world.size() >= 1); + REQUIRE(world.rank() >= 0); + + communicator duplicate{world}; + int comparison = MPI_UNEQUAL; + REQUIRE(MPI_Comm_compare(world.native_handle(), duplicate.native_handle(), + &comparison) == MPI_SUCCESS); + REQUIRE(comparison == MPI_CONGRUENT); + + MPI_Errhandler handler = MPI_ERRHANDLER_NULL; + REQUIRE(MPI_Comm_get_errhandler(duplicate.native_handle(), &handler) == + MPI_SUCCESS); + REQUIRE(handler == MPI_ERRORS_RETURN); + REQUIRE(MPI_Errhandler_free(&handler) == MPI_SUCCESS); + + int dimensions[] = {world.size()}; + int periods[] = {0}; + MPI_Comm cartesian = MPI_COMM_NULL; + REQUIRE(MPI_Cart_create(world.native_handle(), 1, dimensions, periods, 0, + &cartesian) == MPI_SUCCESS); + { + topology duplicate_topology{communicator_view{cartesian}}; + REQUIRE(duplicate_topology.view().size() == world.size()); + } + REQUIRE(MPI_Comm_free(&cartesian) == MPI_SUCCESS); +} + +TEST_CASE("exception barrier captures failures without letting them escape", + "[unit][mpi]") { + REQUIRE(runtime_is_active()); + + std::exception_ptr captured; + run_with_exception_barrier( + [] { throw std::runtime_error{"boundary failure"}; }, + [&](std::exception_ptr failure) noexcept { captured = failure; }); + + REQUIRE(captured != nullptr); + REQUIRE_THROWS_WITH(std::rethrow_exception(captured), "boundary failure"); +} + +TEST_CASE("exported partition boundary is non-throwing", "[unit][mpi]") { + using partition_function = + void(idxtype*, idxtype*, idxtype*, idxtype*, idxtype*, int*, double*, + bool, int, int, int*, idxtype*, MPI_Comm*) noexcept; + STATIC_REQUIRE( + std::is_same_v); +} + +TEST_CASE("explicit tuple wire metadata preserves order and array-safe extent", + "[unit][mpi]") { + STATIC_REQUIRE(parhip::mpi::mpi_wire_datatype); + STATIC_REQUIRE(std::is_standard_layout_v); + STATIC_REQUIRE(std::is_trivially_copyable_v); + STATIC_REQUIRE( + std::get<0>(parhip::mpi::wire_members::value) == + &test_support::wire_entry::id); + STATIC_REQUIRE( + std::get<1>(parhip::mpi::wire_members::value) == + &test_support::wire_entry::owner); + STATIC_REQUIRE( + std::get<2>(parhip::mpi::wire_members::value) == + &test_support::wire_entry::weight); + + auto datatype = make_mpi_datatype(); + REQUIRE(datatype.owns_handle()); + + MPI_Aint lower_bound = -1; + MPI_Aint extent = -1; + REQUIRE(MPI_Type_get_extent(datatype.native_handle(), &lower_bound, + &extent) == MPI_SUCCESS); + REQUIRE(lower_bound == 0); + REQUIRE(extent == static_cast(sizeof(test_support::wire_entry))); +} + +TEST_CASE("wire-record exchange needs no default constructor", "[unit][mpi]") { + STATIC_REQUIRE(parhip::mpi::detail::is_implicit_lifetime_v< + test_support::non_default_wire_entry>); + STATIC_REQUIRE( + parhip::mpi::mpi_wire_datatype); + STATIC_REQUIRE( + std::is_standard_layout_v); + STATIC_REQUIRE( + std::is_trivially_copyable_v); + + auto datatype = make_mpi_datatype(); + REQUIRE(datatype.owns_handle()); + + communicator_view const world{MPI_COMM_WORLD}; + auto const rank = world.rank(); + std::vector> segments( + static_cast(world.size())); + segments[static_cast(rank)].emplace_back( + static_cast(rank + 1), rank); + + auto received = all_to_all_v( + segmented_buffer::from_segments( + segments), + world); + + REQUIRE(std::ranges::equal(received.segment(static_cast(rank)), + segments[static_cast(rank)])); +} + +TEST_CASE("segmented buffers expose canonical contiguous spans", + "[unit][mpi]") { + auto buffer = segmented_buffer::from_segments( + std::vector>{{1, 2}, {}, {3, 4, 5}}); + + REQUIRE( + std::ranges::equal(buffer.storage(), std::vector{1, 2, 3, 4, 5})); + REQUIRE(buffer.counts() == std::vector{2, 0, 3}); + REQUIRE(buffer.offsets() == std::vector{0, 2, 2}); + REQUIRE(std::ranges::equal(buffer.segment(0), std::array{1, 2})); + REQUIRE(buffer.segment(1).empty()); + REQUIRE(std::ranges::equal(buffer.segment(2), std::array{3, 4, 5})); +} + +TEST_CASE("dense exchange preserves all-empty segments", "[unit][mpi]") { + communicator_view const world{MPI_COMM_WORLD}; + auto sends = segmented_buffer::from_segments( + std::vector>(static_cast(world.size()))); + + auto received = all_to_all_v(std::move(sends), world); + + REQUIRE(received.storage().empty()); + REQUIRE( + received.has_canonical_layout(static_cast(world.size()))); + REQUIRE(std::ranges::all_of(received.counts(), + [](auto count) { return count == 0; })); +} + +TEST_CASE("dense exchange preserves self-only wire-record arrays", + "[unit][mpi]") { + communicator_view const world{MPI_COMM_WORLD}; + auto const rank = world.rank(); + std::vector> segments( + static_cast(world.size())); + segments[static_cast(rank)] = { + {static_cast(rank * 10 + 1), rank, 1.25}, + {static_cast(rank * 10 + 2), rank, 2.5}}; + + auto received = all_to_all_v( + segmented_buffer::from_segments(segments), + world); + + REQUIRE( + received.has_canonical_layout(static_cast(world.size()))); + for (int source = 0; source < world.size(); ++source) { + if (source == rank) { + REQUIRE( + std::ranges::equal(received.segment(static_cast(source)), + segments[static_cast(rank)])); + } else { + REQUIRE(received.segment(static_cast(source)).empty()); + } + } +} + +TEST_CASE("dense uneven exchange returns canonical source segments", + "[unit][mpi]") { + communicator_view const world{MPI_COMM_WORLD}; + auto const rank = world.rank(); + std::vector> segments( + static_cast(world.size())); + for (int destination = 0; destination < world.size(); ++destination) { + auto const count = rank + destination; + for (int index = 0; index < count; ++index) { + segments[static_cast(destination)].push_back( + rank * 10'000 + destination * 100 + index); + } + } + + auto received = + all_to_all_v(segmented_buffer::from_segments(segments), world); + + REQUIRE( + received.has_canonical_layout(static_cast(world.size()))); + for (int source = 0; source < world.size(); ++source) { + std::vector expected; + for (int index = 0; index < source + rank; ++index) { + expected.push_back(source * 10'000 + rank * 100 + index); + } + REQUIRE(std::ranges::equal( + received.segment(static_cast(source)), expected)); + } +} + +TEST_CASE("zero-local-work ranks still participate in dense exchange", + "[unit][mpi]") { + communicator_view const world{MPI_COMM_WORLD}; + auto const rank = world.rank(); + std::vector> segments( + static_cast(world.size())); + if (rank != 0) { + segments[0].push_back(rank * 7); + } + + auto received = + all_to_all_v(segmented_buffer::from_segments(segments), world); + + if (rank == 0) { + REQUIRE(received.counts()[0] == 0); + for (int source = 1; source < world.size(); ++source) { + REQUIRE( + std::ranges::equal(received.segment(static_cast(source)), + std::array{source * 7})); + } + } else { + REQUIRE(received.storage().empty()); + } +} + +TEST_CASE("dense validation failure propagates to every rank", "[unit][mpi]") { + communicator_view const world{MPI_COMM_WORLD}; + + std::vector counts(static_cast(world.size()), 0); + std::vector offsets(static_cast(world.size()), 0); + if (world.rank() == 0) { + offsets[0] = 1; + } + segmented_buffer malformed{{}, std::move(counts), std::move(offsets)}; + + require_collective_semantic_error( + [&] { static_cast(all_to_all_v(std::move(malformed), world)); }, + "all_to_all_v collective input validation failed", world); +} + +TEST_CASE("collective validation helper throws one exact common semantic error", + "[unit][mpi][failure-policy][semantic][validation]") { + communicator_view const world{MPI_COMM_WORLD}; + require_collective_semantic_error( + [&] { + validate_collectively(world.rank() != 0, world, + "adapter collective validation failed"); + }, + "adapter collective validation failed", world); +} + +TEST_CASE("collective value agreement is exact or succeeds at one rank", + "[unit][mpi][failure-policy][semantic][agreement]") { + communicator_view const world{MPI_COMM_WORLD}; + if (world.size() == 1) { + semantic_error_protocol_probe::activation observation{}; + REQUIRE(agree_collectively(17, world, "adapter value agreement failed") == + 17); + REQUIRE(semantic_error_protocol_probe::error_string_calls == 0); + return; + } + + require_collective_semantic_error( + [&] { + static_cast(agree_collectively(world.rank(), world, + "adapter value agreement failed")); + }, + "adapter value agreement failed", world); +} + +TEST_CASE("dense options reject zero ceiling collectively", "[unit][mpi]") { + communicator_view const world{MPI_COMM_WORLD}; + auto segments = + std::vector>(static_cast(world.size())); + require_collective_semantic_error( + [&] { + static_cast(all_to_all_v( + segmented_buffer::from_segments(segments), world, + collective_options{.mpi3_round_ceiling = 0, .force_mpi3 = true})); + }, + "all_to_all_v collective options must match and use a nonzero MPI-3 " + "ceiling", + world); +} + +TEST_CASE("dense options agree or reject rank skew collectively", + "[unit][mpi]") { + communicator_view const world{MPI_COMM_WORLD}; + auto segments = + std::vector>(static_cast(world.size())); + if (world.size() == 1) { + semantic_error_protocol_probe::activation observation{}; + auto received = all_to_all_v( + segmented_buffer::from_segments(segments), world, + collective_options{.mpi3_round_ceiling = 2, .force_mpi3 = true}); + REQUIRE(received.storage().empty()); + REQUIRE(semantic_error_protocol_probe::error_string_calls == 0); + return; + } + + auto const options = collective_options{ + .mpi3_round_ceiling = world.rank() == 0 ? 1U : 2U, .force_mpi3 = true}; + require_collective_semantic_error( + [&] { + static_cast(all_to_all_v( + segmented_buffer::from_segments(segments), world, options)); + }, + "all_to_all_v collective options must match and use a nonzero MPI-3 " + "ceiling", + world); +} + +TEST_CASE("forced MPI-3 bounded rounds preserve every element", "[unit][mpi]") { + communicator_view const world{MPI_COMM_WORLD}; + auto const rank = world.rank(); + std::vector> segments( + static_cast(world.size())); + for (int destination = 0; destination < world.size(); ++destination) { + for (int index = 0; index < 5; ++index) { + segments[static_cast(destination)].push_back( + rank * 10'000 + destination * 100 + index); + } + } + + auto received = all_to_all_v( + segmented_buffer::from_segments(segments), world, + collective_options{.mpi3_round_ceiling = 2, .force_mpi3 = true}); + + for (int source = 0; source < world.size(); ++source) { + std::array expected{ + source * 10'000 + rank * 100, source * 10'000 + rank * 100 + 1, + source * 10'000 + rank * 100 + 2, source * 10'000 + rank * 100 + 3, + source * 10'000 + rank * 100 + 4}; + REQUIRE(std::ranges::equal( + received.segment(static_cast(source)), expected)); + } +} + +TEST_CASE("MPI-3 dense phase pairing is exact at the int rank limit", + "[unit][mpi][mpi3][bounded]") { + constexpr auto size = + static_cast(std::numeric_limits::max()); + constexpr auto last = size - 1; + + constexpr auto zero_phase = + parhip::mpi::detail::dense_phase_peers(last, size, 0); + STATIC_REQUIRE(zero_phase.destination == last); + STATIC_REQUIRE(zero_phase.source == last); + + constexpr auto first_phase = + parhip::mpi::detail::dense_phase_peers(last, size, 1); + STATIC_REQUIRE(first_phase.destination == 0); + STATIC_REQUIRE(first_phase.source == size - 2); + + constexpr auto last_phase = + parhip::mpi::detail::dense_phase_peers(last, size, last); + STATIC_REQUIRE(last_phase.destination == size - 2); + STATIC_REQUIRE(last_phase.source == 0); + + constexpr auto rank_zero_last_phase = + parhip::mpi::detail::dense_phase_peers(0, size, last); + STATIC_REQUIRE(rank_zero_last_phase.destination == last); + STATIC_REQUIRE(rank_zero_last_phase.source == 1); +} + +TEST_CASE("forced MPI-3 rounds preserve self-only source segments", + "[unit][mpi]") { + communicator_view const world{MPI_COMM_WORLD}; + auto const rank = world.rank(); + std::vector> segments( + static_cast(world.size())); + for (int index = 0; index < 5; ++index) { + segments[static_cast(rank)].push_back(rank * 100 + index); + } + + auto received = all_to_all_v( + segmented_buffer::from_segments(segments), world, + collective_options{.mpi3_round_ceiling = 2, .force_mpi3 = true}); + + for (int source = 0; source < world.size(); ++source) { + auto const expected = source == rank + ? segments[static_cast(rank)] + : std::vector{}; + REQUIRE(std::ranges::equal( + received.segment(static_cast(source)), expected)); + } +} + +TEST_CASE("forced MPI-3 rounds preserve uneven asymmetric segments", + "[unit][mpi]") { + communicator_view const world{MPI_COMM_WORLD}; + auto const rank = world.rank(); + std::vector> segments( + static_cast(world.size())); + for (int destination = 0; destination < world.size(); ++destination) { + auto const count = 3 + rank + 2 * destination; + for (int index = 0; index < count; ++index) { + segments[static_cast(destination)].push_back( + rank * 10'000 + destination * 100 + index); + } + } + + auto received = all_to_all_v( + segmented_buffer::from_segments(segments), world, + collective_options{.mpi3_round_ceiling = 2, .force_mpi3 = true}); + + for (int source = 0; source < world.size(); ++source) { + std::vector expected; + auto const count = 3 + source + 2 * rank; + for (int index = 0; index < count; ++index) { + expected.push_back(source * 10'000 + rank * 100 + index); + } + REQUIRE(std::ranges::equal( + received.segment(static_cast(source)), expected)); + } +} + +TEST_CASE("forced MPI-3 rounds retain zero-work rank participation", + "[unit][mpi]") { + communicator_view const world{MPI_COMM_WORLD}; + auto const rank = world.rank(); + std::vector> segments( + static_cast(world.size())); + if (rank != 0) { + for (int destination = 1; destination < world.size(); ++destination) { + for (int index = 0; index < 5; ++index) { + segments[static_cast(destination)].push_back( + rank * 10'000 + destination * 100 + index); + } + } + } + + auto received = all_to_all_v( + segmented_buffer::from_segments(segments), world, + collective_options{.mpi3_round_ceiling = 2, .force_mpi3 = true}); + + if (rank == 0) { + REQUIRE(received.storage().empty()); + } else { + REQUIRE(received.segment(0).empty()); + for (int source = 1; source < world.size(); ++source) { + std::array expected{ + source * 10'000 + rank * 100, source * 10'000 + rank * 100 + 1, + source * 10'000 + rank * 100 + 2, source * 10'000 + rank * 100 + 3, + source * 10'000 + rank * 100 + 4}; + REQUIRE(std::ranges::equal( + received.segment(static_cast(source)), expected)); + } + } +} + +TEST_CASE("distributed graph preserves zero-degree ranks", + "[unit][mpi][neighbor][topology][zero]") { + communicator_view const world{MPI_COMM_WORLD}; + + neighborhood_protocol_probe::reset(); + neighborhood_protocol_probe::active = true; + distributed_graph graph{world, {}}; + neighborhood_protocol_probe::active = false; + + REQUIRE(graph.sources().empty()); + REQUIRE(graph.destinations().empty()); + REQUIRE_FALSE(graph.source_index(world.rank()).has_value()); + REQUIRE_FALSE(graph.destination_index(world.rank()).has_value()); + REQUIRE(neighborhood_protocol_probe::dist_graph_create_calls == 1); + + int topology_kind = MPI_UNDEFINED; + REQUIRE(MPI_Topo_test(graph.native_handle(), &topology_kind) == MPI_SUCCESS); + REQUIRE(topology_kind == MPI_DIST_GRAPH); + + MPI_Errhandler handler = MPI_ERRHANDLER_NULL; + REQUIRE(MPI_Comm_get_errhandler(graph.native_handle(), &handler) == + MPI_SUCCESS); + REQUIRE(handler == MPI_ERRORS_RETURN); + REQUIRE(MPI_Errhandler_free(&handler) == MPI_SUCCESS); +} + +TEST_CASE("distributed graph does not alter its caller error handler", + "[unit][mpi][neighbor][topology][handler]") { + communicator_view const world{MPI_COMM_WORLD}; + communicator caller{world}; + REQUIRE(MPI_Comm_set_errhandler(caller.native_handle(), + MPI_ERRORS_ARE_FATAL) == MPI_SUCCESS); + + distributed_graph graph{caller.view(), {world.rank()}}; + + MPI_Errhandler caller_handler = MPI_ERRHANDLER_NULL; + REQUIRE(MPI_Comm_get_errhandler(caller.native_handle(), &caller_handler) == + MPI_SUCCESS); + REQUIRE(caller_handler == MPI_ERRORS_ARE_FATAL); + REQUIRE(MPI_Errhandler_free(&caller_handler) == MPI_SUCCESS); + + MPI_Errhandler graph_handler = MPI_ERRHANDLER_NULL; + REQUIRE(MPI_Comm_get_errhandler(graph.native_handle(), &graph_handler) == + MPI_SUCCESS); + REQUIRE(graph_handler == MPI_ERRORS_RETURN); + REQUIRE(MPI_Errhandler_free(&graph_handler) == MPI_SUCCESS); +} + +TEST_CASE("distributed graph normalizes unsorted duplicate destinations", + "[unit][mpi][neighbor][topology][normalization]") { + communicator_view const world{MPI_COMM_WORLD}; + auto const rank = world.rank(); + auto const next = (rank + 1) % world.size(); + distributed_graph graph{world, {next, rank, next, rank}}; + + auto actual_destinations = std::vector(graph.destinations().begin(), + graph.destinations().end()); + std::ranges::sort(actual_destinations); + auto expected_destinations = std::vector{rank}; + if (next != rank) { + expected_destinations.push_back(next); + std::ranges::sort(expected_destinations); + } + REQUIRE(actual_destinations == expected_destinations); + + auto const previous = (rank - 1 + world.size()) % world.size(); + auto actual_sources = + std::vector(graph.sources().begin(), graph.sources().end()); + std::ranges::sort(actual_sources); + auto expected_sources = std::vector{rank}; + if (previous != rank) { + expected_sources.push_back(previous); + std::ranges::sort(expected_sources); + } + REQUIRE(actual_sources == expected_sources); + + for (std::size_t index = 0; index < graph.destinations().size(); ++index) { + REQUIRE(graph.destination_index(graph.destinations()[index]) == index); + } + for (std::size_t index = 0; index < graph.sources().size(); ++index) { + REQUIRE(graph.source_index(graph.sources()[index]) == index); + } + REQUIRE_FALSE(graph.source_index(world.size()).has_value()); + REQUIRE_FALSE(graph.destination_index(world.size()).has_value()); + + auto indegree = 0; + auto outdegree = 0; + auto weighted = 0; + REQUIRE(MPI_Dist_graph_neighbors_count(graph.native_handle(), &indegree, + &outdegree, &weighted) == MPI_SUCCESS); + auto queried_sources = std::vector(static_cast(indegree)); + auto queried_destinations = + std::vector(static_cast(outdegree)); + REQUIRE(MPI_Dist_graph_neighbors(graph.native_handle(), indegree, + queried_sources.data(), MPI_UNWEIGHTED, + outdegree, queried_destinations.data(), + MPI_UNWEIGHTED) == MPI_SUCCESS); + REQUIRE(std::ranges::equal(graph.sources(), queried_sources)); + REQUIRE(std::ranges::equal(graph.destinations(), queried_destinations)); +} + +TEST_CASE( + "distributed graph represents asymmetric source destination and isolated " + "ranks", + "[unit][mpi][neighbor][topology][asymmetric]") { + communicator_view const world{MPI_COMM_WORLD}; + if (world.size() < 3) { + return; + } + + auto outgoing = std::vector{}; + if (world.rank() == 0) { + for (int destination = 1; destination < world.size() - 1; ++destination) { + outgoing.push_back(destination); + } + } + distributed_graph graph{world, std::move(outgoing)}; + + if (world.rank() == 0) { + REQUIRE(graph.sources().empty()); + REQUIRE(graph.destinations().size() == + static_cast(world.size() - 2)); + } else if (world.rank() == world.size() - 1) { + REQUIRE(graph.sources().empty()); + REQUIRE(graph.destinations().empty()); + } else { + REQUIRE(std::ranges::equal(graph.sources(), std::array{0})); + REQUIRE(graph.destinations().empty()); + } +} + +TEST_CASE("distributed graph owns one movable communicator", + "[unit][mpi][neighbor][topology][ownership]") { + STATIC_REQUIRE_FALSE(std::is_copy_constructible_v); + STATIC_REQUIRE(std::is_move_constructible_v); + STATIC_REQUIRE_FALSE(std::is_copy_assignable_v); + STATIC_REQUIRE(std::is_move_assignable_v); + + communicator_view const world{MPI_COMM_WORLD}; + distributed_graph original{world, {world.rank()}}; + distributed_graph moved{std::move(original)}; + REQUIRE(original.native_handle() == MPI_COMM_NULL); + REQUIRE(moved.native_handle() != MPI_COMM_NULL); + + distributed_graph replacement{world, {}}; + replacement = std::move(moved); + REQUIRE(moved.native_handle() == MPI_COMM_NULL); + REQUIRE( + std::ranges::equal(replacement.destinations(), std::array{world.rank()})); +} + +TEST_CASE( + "distributed graph rejects an invalid destination collectively before " + "creation", + "[unit][mpi][neighbor][topology][failure]") { + communicator_view const world{MPI_COMM_WORLD}; + auto outgoing = std::vector{}; + if (world.rank() == 0) { + outgoing.push_back(world.size()); + } + + neighborhood_protocol_probe::reset(); + neighborhood_protocol_probe::active = true; + require_collective_semantic_error( + [&] { + distributed_graph invalid{world, outgoing}; + static_cast(invalid); + }, + "distributed graph destination validation failed", world); + neighborhood_protocol_probe::active = false; + REQUIRE(neighborhood_protocol_probe::dist_graph_create_calls == 0); +} + +TEST_CASE( + "neighborhood exchange preserves an all-zero topology without point to " + "point calls", + "[unit][mpi][neighbor][exchange][zero]") { + communicator_view const world{MPI_COMM_WORLD}; + distributed_graph graph{world, {}}; + auto sends = + segmented_buffer::from_segments(std::vector>{}); + + neighborhood_protocol_probe::reset(); + neighborhood_protocol_probe::active = true; + auto received = neighbor_all_to_all_v(std::move(sends), graph); + neighborhood_protocol_probe::active = false; + + REQUIRE(received.storage().empty()); + REQUIRE(received.segment_count() == 0); + REQUIRE(neighborhood_protocol_probe::neighbor_count_calls == 1); + REQUIRE(neighborhood_protocol_probe::neighbor_payload_calls + + neighborhood_protocol_probe::neighbor_payload_c_calls == + 1); + REQUIRE(neighborhood_protocol_probe::point_to_point_calls == 0); +} + +TEST_CASE("neighborhood exchange preserves self-only typed arrays", + "[unit][mpi][neighbor][exchange][self]") { + communicator_view const world{MPI_COMM_WORLD}; + auto const rank = world.rank(); + distributed_graph graph{world, {rank}}; + auto const expected = std::vector{ + {static_cast(rank * 10 + 1), rank, 1.25}, + {static_cast(rank * 10 + 2), rank, 2.5}}; + auto sends = segmented_buffer::from_segments( + std::vector>{expected}); + + auto received = neighbor_all_to_all_v(std::move(sends), graph); + + REQUIRE(std::ranges::equal(graph.sources(), std::array{rank})); + REQUIRE(received.segment_count() == 1); + REQUIRE(std::ranges::equal(received.segment(0), expected)); +} + +TEST_CASE("neighborhood exchange follows authoritative queried neighbor order", + "[unit][mpi][neighbor][exchange][order][uneven]") { + communicator_view const world{MPI_COMM_WORLD}; + auto const rank = world.rank(); + auto const next = (rank + 1) % world.size(); + distributed_graph graph{world, {next, rank}}; + + auto segments = std::vector>{}; + segments.reserve(graph.destinations().size()); + for (auto const destination : graph.destinations()) { + auto values = std::vector{}; + auto const count = (rank + destination) % 3; + for (int index = 0; index < count; ++index) { + values.push_back(rank * 10'000 + destination * 100 + index); + } + segments.push_back(std::move(values)); + } + + auto received = neighbor_all_to_all_v( + segmented_buffer::from_segments(segments), graph); + + REQUIRE(received.segment_count() == graph.sources().size()); + for (std::size_t index = 0; index < graph.sources().size(); ++index) { + auto const source = graph.sources()[index]; + auto expected = std::vector{}; + auto const count = (source + rank) % 3; + for (int element = 0; element < count; ++element) { + expected.push_back(source * 10'000 + rank * 100 + element); + } + REQUIRE(std::ranges::equal(received.segment(index), expected)); + } +} + +TEST_CASE("neighborhood exchange supports an asymmetric star", + "[unit][mpi][neighbor][exchange][asymmetric]") { + communicator_view const world{MPI_COMM_WORLD}; + if (world.size() < 3) { + return; + } + auto outgoing = std::vector{}; + if (world.rank() == 0) { + for (int destination = 1; destination < world.size() - 1; ++destination) { + outgoing.push_back(destination); + } + } + distributed_graph graph{world, std::move(outgoing)}; + + auto segments = std::vector>{}; + for (auto const destination : graph.destinations()) { + auto values = std::vector{}; + for (int index = 0; index < destination; ++index) { + values.push_back(destination * 100 + index); + } + segments.push_back(std::move(values)); + } + auto received = neighbor_all_to_all_v( + segmented_buffer::from_segments(segments), graph); + + if (world.rank() == 0 || world.rank() == world.size() - 1) { + REQUIRE(received.storage().empty()); + } else { + REQUIRE(std::ranges::equal(graph.sources(), std::array{0})); + auto expected = std::vector{}; + for (int index = 0; index < world.rank(); ++index) { + expected.push_back(world.rank() * 100 + index); + } + REQUIRE(std::ranges::equal(received.segment(0), expected)); + } +} + +TEST_CASE("neighborhood exchange supports a reversed asymmetric star", + "[unit][mpi][neighbor][exchange][asymmetric]") { + communicator_view const world{MPI_COMM_WORLD}; + if (world.size() < 3) { + return; + } + auto outgoing = world.rank() == 0 ? std::vector{} : std::vector{0}; + distributed_graph graph{world, std::move(outgoing)}; + + auto segments = std::vector>{}; + for (auto const destination : graph.destinations()) { + segments.push_back({world.rank() * 100 + destination}); + } + auto received = neighbor_all_to_all_v( + segmented_buffer::from_segments(segments), graph); + + if (world.rank() == 0) { + REQUIRE(graph.destinations().empty()); + REQUIRE(received.segment_count() == + static_cast(world.size() - 1)); + for (std::size_t index = 0; index < graph.sources().size(); ++index) { + REQUIRE(std::ranges::equal(received.segment(index), + std::array{graph.sources()[index] * 100})); + } + } else { + REQUIRE(graph.sources().empty()); + REQUIRE(received.storage().empty()); + } +} + +TEST_CASE("forced MPI-3 neighborhood rounds preserve asymmetric payloads", + "[unit][mpi][neighbor][exchange][mpi3][bounded]") { + communicator_view const world{MPI_COMM_WORLD}; + auto outgoing = std::vector{}; + if (world.size() == 1) { + outgoing.push_back(0); + } else if (world.rank() == 0) { + for (int destination = 1; destination < world.size(); ++destination) { + outgoing.push_back(destination); + } + } + distributed_graph graph{world, std::move(outgoing)}; + + auto segments = std::vector>{}; + for (auto const destination : graph.destinations()) { + auto values = std::vector{}; + for (int index = 0; index < 5; ++index) { + values.push_back(world.rank() * 10'000 + destination * 100 + index); + } + segments.push_back(std::move(values)); + } + + neighborhood_protocol_probe::reset(); + neighborhood_protocol_probe::active = true; + auto received = neighbor_all_to_all_v( + segmented_buffer::from_segments(segments), graph, + collective_options{.mpi3_round_ceiling = 2, .force_mpi3 = true}); + neighborhood_protocol_probe::active = false; + + if (world.size() == 1 || world.rank() != 0) { + REQUIRE(received.segment_count() == 1); + constexpr auto source = 0; + auto expected = std::vector{}; + for (int index = 0; index < 5; ++index) { + expected.push_back(source * 10'000 + world.rank() * 100 + index); + } + REQUIRE(std::ranges::equal(received.segment(0), expected)); + } else { + REQUIRE(received.storage().empty()); + } + REQUIRE(neighborhood_protocol_probe::neighbor_count_calls == 1); + auto const expected_payload_calls = + 3 * (world.size() == 1 ? 1 : world.size() - 1); + REQUIRE(neighborhood_protocol_probe::neighbor_payload_calls == + expected_payload_calls); + REQUIRE(neighborhood_protocol_probe::neighbor_payload_c_calls == 0); + REQUIRE(neighborhood_protocol_probe::maximum_active_send_segments <= 1); + REQUIRE(neighborhood_protocol_probe::maximum_active_receive_segments <= 1); + REQUIRE(neighborhood_protocol_probe::maximum_payload_count <= 2); + REQUIRE(neighborhood_protocol_probe::nonzero_displacement_calls == 0); + REQUIRE(neighborhood_protocol_probe::point_to_point_calls == 0); +} + +TEST_CASE( + "neighborhood exchange rejects a malformed segment count collectively", + "[unit][mpi][neighbor][exchange][failure]") { + communicator_view const world{MPI_COMM_WORLD}; + distributed_graph graph{world, {world.rank()}}; + auto segments = world.rank() == 0 + ? std::vector>{} + : std::vector>{{world.rank()}}; + + neighborhood_protocol_probe::reset(); + neighborhood_protocol_probe::active = true; + require_collective_semantic_error( + [&] { + static_cast(neighbor_all_to_all_v( + segmented_buffer::from_segments(segments), graph)); + }, + "neighbor_all_to_all_v collective input validation failed", world); + neighborhood_protocol_probe::active = false; + REQUIRE(neighborhood_protocol_probe::neighbor_count_calls == 0); + REQUIRE(neighborhood_protocol_probe::neighbor_payload_calls == 0); + REQUIRE(neighborhood_protocol_probe::neighbor_payload_c_calls == 0); +} + +TEST_CASE("neighborhood exchange rejects mismatched MPI-3 options collectively", + "[unit][mpi][neighbor][exchange][failure][options]") { + communicator_view const world{MPI_COMM_WORLD}; + if (world.size() == 1) { + distributed_graph graph{world, {world.rank()}}; + semantic_error_protocol_probe::activation observation{}; + auto received = neighbor_all_to_all_v( + segmented_buffer::from_segments( + std::vector>{{world.rank()}}), + graph, collective_options{.mpi3_round_ceiling = 1, .force_mpi3 = true}); + REQUIRE(std::ranges::equal(received.segment(0), std::array{world.rank()})); + REQUIRE(semantic_error_protocol_probe::error_string_calls == 0); + return; + } + distributed_graph graph{world, {world.rank()}}; + auto options = collective_options{ + .mpi3_round_ceiling = world.rank() == 0 ? 1U : 2U, .force_mpi3 = true}; + + neighborhood_protocol_probe::reset(); + neighborhood_protocol_probe::active = true; + require_collective_semantic_error( + [&] { + static_cast(neighbor_all_to_all_v( + segmented_buffer::from_segments( + std::vector>{{world.rank()}}), + graph, options)); + }, + "neighbor_all_to_all_v collective options must match and use a nonzero " + "MPI-3 ceiling", + world); + neighborhood_protocol_probe::active = false; + REQUIRE(neighborhood_protocol_probe::neighbor_count_calls == 0); + REQUIRE(neighborhood_protocol_probe::neighbor_payload_calls == 0); + REQUIRE(neighborhood_protocol_probe::neighbor_payload_c_calls == 0); +} + +TEST_CASE("neighborhood exchange rejects a zero MPI-3 ceiling before counts", + "[unit][mpi][neighbor][exchange][failure][options]") { + communicator_view const world{MPI_COMM_WORLD}; + distributed_graph graph{world, {world.rank()}}; + + neighborhood_protocol_probe::reset(); + neighborhood_protocol_probe::active = true; + require_collective_semantic_error( + [&] { + static_cast(neighbor_all_to_all_v( + segmented_buffer::from_segments( + std::vector>{{world.rank()}}), + graph, + collective_options{.mpi3_round_ceiling = 0, .force_mpi3 = true})); + }, + "neighbor_all_to_all_v collective options must match and use a nonzero " + "MPI-3 ceiling", + world); + neighborhood_protocol_probe::active = false; + REQUIRE(neighborhood_protocol_probe::neighbor_count_calls == 0); + REQUIRE(neighborhood_protocol_probe::neighbor_payload_calls == 0); + REQUIRE(neighborhood_protocol_probe::neighbor_payload_c_calls == 0); +} + +TEST_CASE("neighbor large-count capability reflects the generated probe", + "[unit][mpi][neighbor][capability]") { +#if defined(KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C) + STATIC_REQUIRE(parhip::mpi::capabilities::has_neighbor_alltoallv_c == + (KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C != 0)); +#else + FAIL("generated MPI neighborhood capability macro is missing"); +#endif +} + +TEST_CASE("collectively agreed semantic errors are MPI-free and rank-symmetric", + "[unit][mpi][failure-policy][semantic]") { + communicator_view const world{MPI_COMM_WORLD}; + auto const local_is_valid = world.rank() != 0 ? 1 : 0; + auto all_are_valid = 0; + REQUIRE(PMPI_Allreduce(&local_is_valid, &all_are_valid, 1, MPI_INT, MPI_LAND, + world.native_handle()) == MPI_SUCCESS); + REQUIRE(all_are_valid == 0); + + constexpr auto context = std::string_view{"semantic helper symmetry"}; + auto caught = 0; + auto raw_code_matches = 0; + auto context_matches = 0; + auto location_is_retained = 0; + auto const expected_location = std::source_location::current(); + { + semantic_error_protocol_probe::activation observation{}; + try { + parhip::mpi::throw_collectively_agreed_semantic_error( + world.native_handle(), context, expected_location); + } catch (parhip::mpi::mpi_error const& error) { + caught = 1; + raw_code_matches = error.error_code() == MPI_ERR_ARG ? 1 : 0; + context_matches = error.context() == context ? 1 : 0; + auto const actual_location = error.location(); + location_is_retained = + actual_location.line() == expected_location.line() && + actual_location.column() == expected_location.column() && + std::string_view{actual_location.file_name()} == + expected_location.file_name() && + std::string_view{actual_location.function_name()} == + expected_location.function_name() + ? 1 + : 0; + } + } + + auto local = std::array{caught, raw_code_matches, context_matches, + location_is_retained, + semantic_error_protocol_probe::error_string_calls}; + auto global = std::array{0, 0, 0, 0, 0}; + REQUIRE(PMPI_Allreduce(local.data(), global.data(), + static_cast(local.size()), MPI_INT, MPI_SUM, + world.native_handle()) == MPI_SUCCESS); + REQUIRE(global == std::array{world.size(), world.size(), world.size(), + world.size(), 0}); +} +} // namespace diff --git a/parallel/parallel_src/tests/communication/mpi_async_capacity_failure_probe.cpp b/parallel/parallel_src/tests/communication/mpi_async_capacity_failure_probe.cpp new file mode 100644 index 00000000..4645a857 --- /dev/null +++ b/parallel/parallel_src/tests/communication/mpi_async_capacity_failure_probe.cpp @@ -0,0 +1,503 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_adapter.h" +#include "kahip_mpi_capabilities.h" + +namespace async_capacity_probe { +struct alignas(64) wire_record final { + std::uint64_t value; +}; + +struct byte_wire_record final { + unsigned char value; +}; +} // namespace async_capacity_probe + +template <> +struct parhip::mpi::wire_members { + inline static constexpr auto value = + std::tuple{&async_capacity_probe::wire_record::value}; +}; + +template <> +struct parhip::mpi::wire_members { + inline static constexpr auto value = + std::tuple{&async_capacity_probe::byte_wire_record::value}; +}; + +static_assert(std::is_standard_layout_v); +static_assert(std::is_trivially_copyable_v); +static_assert(alignof(async_capacity_probe::wire_record) == 64); +static_assert( + std::is_standard_layout_v); +static_assert( + std::is_trivially_copyable_v); +static_assert(sizeof(async_capacity_probe::byte_wire_record) == 1); + +namespace { +enum class failure_mode : std::uint8_t { + one_shot_receive_offset, + one_shot_receive_byte, + fixed_send_offset, + fixed_send_byte, +}; + +auto selected_mode = failure_mode::one_shot_receive_offset; +auto cached_rank = -1; +auto cached_size = -1; +auto track_next_duplicate = false; +auto operation_communicator = MPI_COMM_NULL; +auto graph_communicator = MPI_COMM_NULL; +auto injection_is_armed = false; +auto payload_allocation_watch = false; +auto error_string_attempts = 0; +auto cleanup_attempts = 0; +auto capacity_bor_attempts = 0; +auto backend_band_attempts = 0; +auto count_exchange_attempts = 0; +auto payload_allocation_attempts = 0; +auto datatype_attempts = 0; +auto immediate_init_attempts = 0; +auto persistent_init_attempts = 0; +auto payload_collective_attempts = 0; +auto operation_duplicate_attempts = 0; + +[[nodiscard]] auto is_receive_mode() noexcept -> bool { + return selected_mode == failure_mode::one_shot_receive_offset || + selected_mode == failure_mode::one_shot_receive_byte; +} + +[[nodiscard]] auto is_offset_mode() noexcept -> bool { + return selected_mode == failure_mode::one_shot_receive_offset || + selected_mode == failure_mode::fixed_send_offset; +} + +[[nodiscard]] auto affected_name(MPI_Comm communicator) noexcept + -> std::string_view { + if (communicator == operation_communicator) { + return "async-operation"; + } + if (communicator == graph_communicator) { + return "async-graph"; + } + if (communicator == MPI_COMM_WORLD) { + return "world"; + } + return "unexpected"; +} + +[[noreturn]] void forbidden(char const* operation) noexcept { + std::fprintf(stderr, "forbidden async capacity action: %s\n", operation); + std::_Exit(90); +} + +[[noreturn]] void returned_from_failure() noexcept { + std::fputs("returned-from-async-capacity-failure\n", stderr); + std::_Exit(2); +} + +void write_abort_observation(std::string_view affected) noexcept { + constexpr auto buffer_capacity = std::size_t{512}; + static_assert(buffer_capacity <= PIPE_BUF); + auto buffer = std::array{}; + auto const length = std::snprintf( + buffer.data(), buffer.size(), + "observed MPI_Abort rank=%d affected=%.*s error-string-attempts=%d " + "cleanup-attempts=%d capacity-bor-attempts=%d " + "backend-band-attempts=%d count-exchange-attempts=%d " + "payload-allocation-attempts=%d datatype-attempts=%d " + "immediate-init-attempts=%d persistent-init-attempts=%d " + "payload-collective-attempts=%d operation-duplicate-attempts=%d\n", + cached_rank, static_cast(affected.size()), affected.data(), + error_string_attempts, cleanup_attempts, capacity_bor_attempts, + backend_band_attempts, count_exchange_attempts, + payload_allocation_attempts, datatype_attempts, immediate_init_attempts, + persistent_init_attempts, payload_collective_attempts, + operation_duplicate_attempts); + if (length < 0 || static_cast(length) >= buffer.size() || + ::write(STDERR_FILENO, buffer.data(), static_cast(length)) != + length) { + std::_Exit(89); + } +} + +[[nodiscard]] auto allocate_aligned(std::size_t size, std::size_t alignment) + -> void* { + auto const allocation_size = std::max(size, std::size_t{1}); + if (alignment <= alignof(std::max_align_t)) { + if (auto* allocation = std::malloc(allocation_size); + allocation != nullptr) { + return allocation; + } + throw std::bad_alloc{}; + } + auto* allocation = static_cast(nullptr); + if (posix_memalign(&allocation, alignment, allocation_size) != 0) { + throw std::bad_alloc{}; + } + return allocation; +} +} // namespace + +static_assert(noexcept(write_abort_observation({}))); + +void* operator new(std::size_t size, std::align_val_t alignment) { + if (payload_allocation_watch) { + ++payload_allocation_attempts; + forbidden("aligned payload allocation"); + } + return allocate_aligned(size, static_cast(alignment)); +} + +void operator delete(void* allocation, std::align_val_t) noexcept { + std::free(allocation); +} + +void operator delete(void* allocation, std::size_t, std::align_val_t) noexcept { + std::free(allocation); +} + +extern "C" int MPI_Comm_dup(MPI_Comm communicator, + MPI_Comm* duplicate_communicator) { + auto const result = PMPI_Comm_dup(communicator, duplicate_communicator); + if (payload_allocation_watch) { + ++operation_duplicate_attempts; + if (operation_duplicate_attempts > 1) { + forbidden("extra async operation duplicate"); + } + } + if (result == MPI_SUCCESS && track_next_duplicate && + duplicate_communicator != nullptr) { + operation_communicator = *duplicate_communicator; + track_next_duplicate = false; + std::fputs("captured async operation duplicate\n", stderr); + if (!is_receive_mode()) { + injection_is_armed = true; + } + } + return result; +} + +extern "C" int MPI_Neighbor_alltoall(void const* send_buffer, + int send_count, + MPI_Datatype send_datatype, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + auto const result = PMPI_Neighbor_alltoall( + send_buffer, send_count, send_datatype, receive_buffer, receive_count, + receive_datatype, communicator); + if (communicator != operation_communicator) { + return result; + } + ++count_exchange_attempts; + if (result != MPI_SUCCESS || count_exchange_attempts != 1 || + cached_size != 2 || send_count != 1 || receive_count != 1 || + send_datatype != MPI_UINT64_T || receive_datatype != MPI_UINT64_T || + receive_buffer == nullptr) { + forbidden("neighbor count exchange shape"); + } + if (is_receive_mode() && cached_rank == 0) { + auto* counts = static_cast(receive_buffer); + if (is_offset_mode()) { + counts[0] = std::numeric_limits::max(); + counts[1] = std::uint64_t{1}; + std::fputs("injected rank-zero async receive offset capacity\n", stderr); + } else { + counts[0] = std::numeric_limits::max() / + sizeof(async_capacity_probe::wire_record) + + std::uint64_t{1}; + counts[1] = std::uint64_t{0}; + std::fputs("injected rank-zero async receive byte capacity\n", stderr); + } + } + if (is_receive_mode()) { + injection_is_armed = true; + } + return result; +} + +extern "C" int MPI_Allreduce(void const* send_buffer, + void* receive_buffer, + int count, + MPI_Datatype datatype, + MPI_Op operation, + MPI_Comm communicator) { + if (communicator == operation_communicator && count == 2 && + datatype == MPI_UINT64_T && operation == MPI_BOR) { + ++capacity_bor_attempts; + } + if (communicator == operation_communicator && count == 2 && + datatype == MPI_UINT64_T && operation == MPI_BAND) { + ++backend_band_attempts; + } + if (injection_is_armed && communicator == operation_communicator && + ((operation == MPI_BOR && capacity_bor_attempts > 1) || + (operation == MPI_BAND && backend_band_attempts > 1))) { + forbidden("duplicate async capacity/backend reduction"); + } + return PMPI_Allreduce(send_buffer, receive_buffer, count, datatype, operation, + communicator); +} + +extern "C" int MPI_Error_string(int error_code, + char* error_text, + int* error_text_length) { + if (injection_is_armed) { + ++error_string_attempts; + forbidden("MPI_Error_string"); + } + return PMPI_Error_string(error_code, error_text, error_text_length); +} + +extern "C" int MPI_Comm_free(MPI_Comm* communicator) { + if (injection_is_armed && communicator != nullptr && + *communicator == operation_communicator) { + ++cleanup_attempts; + forbidden("MPI_Comm_free(async operation duplicate)"); + } + return PMPI_Comm_free(communicator); +} + +extern "C" int MPI_Abort(MPI_Comm communicator, int) { + if (!injection_is_armed) { + forbidden("MPI_Abort before async capacity injection"); + } + auto const affected = affected_name(communicator); + if (std::fflush(stderr) != 0) { + std::_Exit(89); + } + write_abort_observation(affected); + std::_Exit(86); +} + +void record_datatype_attempt(char const* operation) { + if (injection_is_armed) { + ++datatype_attempts; + forbidden(operation); + } +} + +extern "C" int MPI_Get_address(void const* location, MPI_Aint* address) { + record_datatype_attempt("MPI_Get_address(async datatype)"); + return PMPI_Get_address(location, address); +} + +extern "C" int MPI_Type_create_struct(int count, + int const block_lengths[], + MPI_Aint const displacements[], + MPI_Datatype const datatypes[], + MPI_Datatype* new_datatype) { + record_datatype_attempt("MPI_Type_create_struct(async datatype)"); + return PMPI_Type_create_struct(count, block_lengths, displacements, datatypes, + new_datatype); +} + +extern "C" int MPI_Type_create_resized(MPI_Datatype old_datatype, + MPI_Aint lower_bound, + MPI_Aint extent, + MPI_Datatype* new_datatype) { + record_datatype_attempt("MPI_Type_create_resized(async datatype)"); + return PMPI_Type_create_resized(old_datatype, lower_bound, extent, + new_datatype); +} + +extern "C" int MPI_Type_commit(MPI_Datatype* datatype) { + record_datatype_attempt("MPI_Type_commit(async datatype)"); + return PMPI_Type_commit(datatype); +} + +extern "C" int MPI_Type_free(MPI_Datatype* datatype) { + record_datatype_attempt("MPI_Type_free(async datatype)"); + return PMPI_Type_free(datatype); +} + +void record_immediate_attempt(char const* operation) { + if (injection_is_armed) { + ++immediate_init_attempts; + ++payload_collective_attempts; + forbidden(operation); + } +} + +void record_persistent_attempt(char const* operation) { + if (injection_is_armed) { + ++persistent_init_attempts; + ++payload_collective_attempts; + forbidden(operation); + } +} + +extern "C" int MPI_Ineighbor_alltoallv(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Request* request) { + record_immediate_attempt("MPI_Ineighbor_alltoallv(async payload)"); + return PMPI_Ineighbor_alltoallv(send_buffer, send_counts, send_displacements, + send_datatype, receive_buffer, receive_counts, + receive_displacements, receive_datatype, + communicator, request); +} + +#if KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV_C +extern "C" int MPI_Ineighbor_alltoallv_c(void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Request* request) { + record_immediate_attempt("MPI_Ineighbor_alltoallv_c(async payload)"); + return PMPI_Ineighbor_alltoallv_c( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator, request); +} +#endif + +#if KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT +extern "C" int MPI_Neighbor_alltoallv_init(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Info info, + MPI_Request* request) { + record_persistent_attempt("MPI_Neighbor_alltoallv_init(async payload)"); + return PMPI_Neighbor_alltoallv_init( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator, info, request); +} +#endif + +#if KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT_C +extern "C" int MPI_Neighbor_alltoallv_init_c( + void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Info info, + MPI_Request* request) { + record_persistent_attempt("MPI_Neighbor_alltoallv_init_c(async payload)"); + return PMPI_Neighbor_alltoallv_init_c( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator, info, request); +} +#endif + +namespace { +[[nodiscard]] auto parse_mode(std::string_view mode) -> bool { + if (mode == "one-shot-receive-offset") { + selected_mode = failure_mode::one_shot_receive_offset; + } else if (mode == "one-shot-receive-byte") { + selected_mode = failure_mode::one_shot_receive_byte; + } else if (mode == "fixed-send-offset") { + selected_mode = failure_mode::fixed_send_offset; + } else if (mode == "fixed-send-byte") { + selected_mode = failure_mode::fixed_send_byte; + } else { + return false; + } + return true; +} + +[[noreturn]] void run_receive_failure( + parhip::mpi::distributed_graph const& graph) { + auto segments = std::vector>( + graph.destinations().size(), + std::vector{async_capacity_probe::wire_record{ + .value = static_cast(cached_rank + 1)}}); + auto sends = parhip::mpi::segmented_buffer< + async_capacity_probe::wire_record>::from_segments(segments); + track_next_duplicate = true; + payload_allocation_watch = true; + static_cast( + parhip::mpi::start_neighbor_all_to_all_v(std::move(sends), graph)); + returned_from_failure(); +} + +[[noreturn]] void run_fixed_failure( + parhip::mpi::distributed_graph const& graph) { + auto counts = std::vector(graph.destinations().size(), 1); + if (cached_rank == 0) { + if (selected_mode == failure_mode::fixed_send_offset) { + counts[0] = std::numeric_limits::max(); + counts[1] = std::size_t{1}; + std::fputs("armed rank-zero async fixed-send offset capacity\n", stderr); + } else { + counts[0] = std::numeric_limits::max() / + sizeof(async_capacity_probe::wire_record) + + std::size_t{1}; + counts[1] = std::size_t{0}; + std::fputs("armed rank-zero async fixed-send byte capacity\n", stderr); + } + } + track_next_duplicate = true; + payload_allocation_watch = true; + parhip::mpi::neighbor_all_to_all_v_context + context{graph, std::move(counts)}; + static_cast(context); + returned_from_failure(); +} + +} // namespace + +int main(int argc, char* argv[]) { + if (argc != 2 || !parse_mode(argv[1])) { + std::fputs("usage: mpi_async_capacity_failure_probe MODE\n", stderr); + return 64; + } + if (MPI_Init(&argc, &argv) != MPI_SUCCESS || + PMPI_Comm_rank(MPI_COMM_WORLD, &cached_rank) != MPI_SUCCESS || + PMPI_Comm_size(MPI_COMM_WORLD, &cached_size) != MPI_SUCCESS) { + std::fputs("MPI setup failed before async capacity probe\n", stderr); + return 70; + } + if (cached_size != 2) { + std::fputs("async capacity probe requires exactly two ranks\n", stderr); + return 64; + } + auto graph = parhip::mpi::distributed_graph{ + parhip::mpi::communicator_view{MPI_COMM_WORLD}, {0, 1}}; + graph_communicator = graph.native_handle(); + if (is_receive_mode()) { + run_receive_failure(graph); + } + run_fixed_failure(graph); +} diff --git a/parallel/parallel_src/tests/communication/mpi_async_neighbors_failure_probe.cpp b/parallel/parallel_src/tests/communication/mpi_async_neighbors_failure_probe.cpp new file mode 100644 index 00000000..32ee726f --- /dev/null +++ b/parallel/parallel_src/tests/communication/mpi_async_neighbors_failure_probe.cpp @@ -0,0 +1,667 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_adapter.h" +#include "kahip_mpi_capabilities.h" + +namespace async_failure_support { +struct wire_entry { + std::uint64_t value; + int rank; +}; +} // namespace async_failure_support + +template <> +struct parhip::mpi::wire_members { + inline static constexpr auto value = std::tuple{ + &async_failure_support::wire_entry::value, + &async_failure_support::wire_entry::rank}; +}; + +namespace { +enum class failure_mode { + immediate_init, + test, + wait, + destructor_wait, + persistent_init, + persistent_start, + persistent_test, + persistent_wait, + request_free, + active_restart, + inactive_test, + bounded_inactive_test, + bounded_inactive_wait, + bounded_later_init, + bounded_later_test, + bounded_later_wait, + send_while_active, + receive_while_active, + post_finalize_active_request, + post_finalize_complete_request, + post_finalize_complete_wait, + post_finalize_immediate_context, + post_finalize_persistent_context, + initialized_query, + finalized_query, +}; + +constexpr auto injected_mpi_error = MPI_ERR_OTHER; +constexpr auto injected_lifecycle_error = 17295; +auto selected_mode = failure_mode::immediate_init; +auto operation_communicator = MPI_COMM_NULL; +auto operation_datatype = MPI_DATATYPE_NULL; +auto operation_request = MPI_REQUEST_NULL; +auto failure_was_injected = false; +auto runtime_was_finalized = false; +auto inject_initialized_query = false; +auto inject_finalized_query = false; +auto underlying_request_completed = false; +auto bounded_init_attempts = 0; + +[[nodiscard]] auto mode_name() noexcept -> std::string_view { + switch (selected_mode) { + case failure_mode::immediate_init: + return "immediate-init"; + case failure_mode::test: + return "test"; + case failure_mode::wait: + return "wait"; + case failure_mode::destructor_wait: + return "destructor-wait"; + case failure_mode::persistent_init: + return "persistent-init"; + case failure_mode::persistent_start: + return "persistent-start"; + case failure_mode::persistent_test: + return "persistent-test"; + case failure_mode::persistent_wait: + return "persistent-wait"; + case failure_mode::request_free: + return "request-free"; + case failure_mode::active_restart: + return "active-restart"; + case failure_mode::inactive_test: + return "inactive-test"; + case failure_mode::bounded_inactive_test: + return "bounded-inactive-test"; + case failure_mode::bounded_inactive_wait: + return "bounded-inactive-wait"; + case failure_mode::bounded_later_init: + return "bounded-later-init"; + case failure_mode::bounded_later_test: + return "bounded-later-test"; + case failure_mode::bounded_later_wait: + return "bounded-later-wait"; + case failure_mode::send_while_active: + return "send-while-active"; + case failure_mode::receive_while_active: + return "receive-while-active"; + case failure_mode::post_finalize_active_request: + return "post-finalize-active-request"; + case failure_mode::post_finalize_complete_request: + return "post-finalize-complete-request"; + case failure_mode::post_finalize_complete_wait: + return "post-finalize-complete-wait"; + case failure_mode::post_finalize_immediate_context: + return "post-finalize-immediate-context"; + case failure_mode::post_finalize_persistent_context: + return "post-finalize-persistent-context"; + case failure_mode::initialized_query: + return "initialized-query"; + case failure_mode::finalized_query: + return "finalized-query"; + } + return "unknown"; +} + +[[nodiscard]] auto is_persistent_mode() noexcept -> bool { + return selected_mode == failure_mode::persistent_init || + selected_mode == failure_mode::persistent_start || + selected_mode == failure_mode::persistent_test || + selected_mode == failure_mode::persistent_wait || + selected_mode == failure_mode::request_free || + selected_mode == failure_mode::post_finalize_persistent_context; +} + +[[nodiscard]] auto expects_raw_abort() noexcept -> bool { + return selected_mode == failure_mode::post_finalize_active_request || + selected_mode == failure_mode::post_finalize_complete_request || + selected_mode == failure_mode::post_finalize_complete_wait || + selected_mode == failure_mode::post_finalize_immediate_context || + selected_mode == failure_mode::post_finalize_persistent_context || + selected_mode == failure_mode::initialized_query || + selected_mode == failure_mode::finalized_query; +} + +[[noreturn]] void forbidden_cleanup(char const* operation) noexcept { + auto const mode = mode_name(); + std::fprintf(stderr, "forbidden cleanup after %.*s failure: %s\n", + static_cast(mode.size()), mode.data(), operation); + std::_Exit(90); +} + +void announce_injection() noexcept { + auto const mode = mode_name(); + std::fprintf(stderr, "injecting raw MPI error %d for %.*s\n", + injected_mpi_error, static_cast(mode.size()), mode.data()); + failure_was_injected = true; +} + +[[noreturn]] void observed_raw_abort(int) noexcept { + constexpr char prefix[] = "observed SIGABRT for "; + constexpr char suffix[] = " raw-abort path\n"; + static_cast(::write(STDERR_FILENO, prefix, sizeof(prefix) - 1)); + auto const mode = mode_name(); + static_cast(::write(STDERR_FILENO, mode.data(), mode.size())); + static_cast(::write(STDERR_FILENO, suffix, sizeof(suffix) - 1)); + std::_Exit(86); +} + +void track_operation(MPI_Comm communicator, MPI_Datatype datatype) noexcept { + operation_communicator = communicator; + operation_datatype = datatype; +} + +void track_request(MPI_Request request) noexcept { + operation_request = request; +} + +[[nodiscard]] auto is_operation_request(MPI_Request const* request) noexcept + -> bool { + return request != nullptr && operation_request != MPI_REQUEST_NULL && + *request == operation_request; +} + +void forbid_target_cleanup_when_terminating(char const* operation) { + if (failure_was_injected || runtime_was_finalized) { + forbidden_cleanup(operation); + } +} +} // namespace + +extern "C" int MPI_Ineighbor_alltoallv(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Request* request) { + track_operation(communicator, send_datatype); + if (selected_mode == failure_mode::bounded_later_init || + selected_mode == failure_mode::bounded_later_test || + selected_mode == failure_mode::bounded_later_wait) { + ++bounded_init_attempts; + } + if (selected_mode == failure_mode::immediate_init) { + announce_injection(); + return injected_mpi_error; + } + if (selected_mode == failure_mode::bounded_later_init && + bounded_init_attempts == 2) { + announce_injection(); + return injected_mpi_error; + } + auto const result = PMPI_Ineighbor_alltoallv( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator, request); + if (result == MPI_SUCCESS) { + track_request(*request); + } + return result; +} + +#if KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV_C +extern "C" int MPI_Ineighbor_alltoallv_c(void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Request* request) { + track_operation(communicator, send_datatype); + if (selected_mode == failure_mode::immediate_init) { + announce_injection(); + return injected_mpi_error; + } + auto const result = PMPI_Ineighbor_alltoallv_c( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator, request); + if (result == MPI_SUCCESS) { + track_request(*request); + } + return result; +} +#endif + +#if KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT +extern "C" int MPI_Neighbor_alltoallv_init(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Info info, + MPI_Request* request) { + track_operation(communicator, send_datatype); + if (selected_mode == failure_mode::persistent_init) { + announce_injection(); + return injected_mpi_error; + } + auto const result = PMPI_Neighbor_alltoallv_init( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator, info, request); + if (result == MPI_SUCCESS) { + track_request(*request); + } + return result; +} +#endif + +#if KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT_C +extern "C" int MPI_Neighbor_alltoallv_init_c( + void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Info info, + MPI_Request* request) { + track_operation(communicator, send_datatype); + if (selected_mode == failure_mode::persistent_init) { + announce_injection(); + return injected_mpi_error; + } + auto const result = PMPI_Neighbor_alltoallv_init_c( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator, info, request); + if (result == MPI_SUCCESS) { + track_request(*request); + } + return result; +} +#endif + +extern "C" int MPI_Start(MPI_Request* request) { + if (is_operation_request(request) && + selected_mode == failure_mode::persistent_start) { + announce_injection(); + return injected_mpi_error; + } + return PMPI_Start(request); +} + +extern "C" int MPI_Test(MPI_Request* request, + int* complete, + MPI_Status* status) { + auto const tracked = is_operation_request(request); + if (tracked && (selected_mode == failure_mode::test || + selected_mode == failure_mode::persistent_test)) { + announce_injection(); + return injected_mpi_error; + } + if (tracked && selected_mode == failure_mode::bounded_later_test && + bounded_init_attempts >= 2) { + announce_injection(); + return injected_mpi_error; + } + auto const result = PMPI_Test(request, complete, status); + if (tracked && result == MPI_SUCCESS && *complete != 0 && + selected_mode == failure_mode::post_finalize_active_request) { + underlying_request_completed = true; + *complete = 0; + } + return result; +} + +extern "C" int MPI_Wait(MPI_Request* request, MPI_Status* status) { + if (runtime_was_finalized) { + forbidden_cleanup("MPI_Wait"); + } + auto const tracked = is_operation_request(request); + if (tracked && (selected_mode == failure_mode::wait || + selected_mode == failure_mode::destructor_wait || + selected_mode == failure_mode::persistent_wait)) { + announce_injection(); + return injected_mpi_error; + } + if (tracked && selected_mode == failure_mode::bounded_later_wait && + bounded_init_attempts >= 2) { + announce_injection(); + return injected_mpi_error; + } + forbid_target_cleanup_when_terminating("MPI_Wait"); + return PMPI_Wait(request, status); +} + +extern "C" int MPI_Request_free(MPI_Request* request) { + auto const tracked = is_operation_request(request); + if (tracked) { + forbid_target_cleanup_when_terminating("MPI_Request_free"); + if (selected_mode == failure_mode::request_free) { + announce_injection(); + return injected_mpi_error; + } + } + return PMPI_Request_free(request); +} + +extern "C" int MPI_Type_free(MPI_Datatype* datatype) { + auto const tracked = operation_datatype != MPI_DATATYPE_NULL && + *datatype == operation_datatype; + if (tracked) { + forbid_target_cleanup_when_terminating("MPI_Type_free"); + } + return PMPI_Type_free(datatype); +} + +extern "C" int MPI_Comm_free(MPI_Comm* communicator) { + auto const tracked = operation_communicator != MPI_COMM_NULL && + *communicator == operation_communicator; + if (tracked) { + forbid_target_cleanup_when_terminating("MPI_Comm_free"); + } + return PMPI_Comm_free(communicator); +} + +extern "C" int MPI_Error_string(int error_code, char* text, int* text_length) { + if (expects_raw_abort()) { + forbidden_cleanup("MPI_Error_string"); + } + return PMPI_Error_string(error_code, text, text_length); +} + +extern "C" int MPI_Abort(MPI_Comm communicator, int) { + if (expects_raw_abort()) { + forbidden_cleanup("MPI_Abort"); + } + auto const mode = mode_name(); + auto const affected = communicator == operation_communicator + ? std::string_view{"operation"} + : std::string_view{"unexpected"}; + std::fprintf(stderr, "observed MPI_Abort for %.*s on %.*s communicator\n", + static_cast(mode.size()), mode.data(), + static_cast(affected.size()), affected.data()); + std::_Exit(affected == "operation" ? 86 : 91); +} + +extern "C" int MPI_Initialized(int* flag) { + if (inject_initialized_query) { + inject_initialized_query = false; + std::fprintf(stderr, + "injecting raw lifecycle error %d for MPI_Initialized\n", + injected_lifecycle_error); + return injected_lifecycle_error; + } + return PMPI_Initialized(flag); +} + +extern "C" int MPI_Finalized(int* flag) { + if (inject_finalized_query) { + inject_finalized_query = false; + std::fprintf(stderr, "injecting raw lifecycle error %d for MPI_Finalized\n", + injected_lifecycle_error); + return injected_lifecycle_error; + } + return PMPI_Finalized(flag); +} + +namespace { +using async_failure_support::wire_entry; +using parhip::mpi::collective_options; +using parhip::mpi::communicator_view; +using parhip::mpi::context_options; +using parhip::mpi::distributed_graph; +using parhip::mpi::neighbor_all_to_all_v_context; +using parhip::mpi::persistence_policy; +using parhip::mpi::segmented_buffer; +using parhip::mpi::start_neighbor_all_to_all_v; + +[[nodiscard]] auto parse_mode(std::string_view mode) -> bool { +#define KAHIP_PARSE_MODE(text, value) \ + if (mode == text) { \ + selected_mode = failure_mode::value; \ + return true; \ + } + KAHIP_PARSE_MODE("immediate-init", immediate_init) + KAHIP_PARSE_MODE("test", test) + KAHIP_PARSE_MODE("wait", wait) + KAHIP_PARSE_MODE("destructor-wait", destructor_wait) + KAHIP_PARSE_MODE("persistent-init", persistent_init) + KAHIP_PARSE_MODE("persistent-start", persistent_start) + KAHIP_PARSE_MODE("persistent-test", persistent_test) + KAHIP_PARSE_MODE("persistent-wait", persistent_wait) + KAHIP_PARSE_MODE("request-free", request_free) + KAHIP_PARSE_MODE("active-restart", active_restart) + KAHIP_PARSE_MODE("inactive-test", inactive_test) + KAHIP_PARSE_MODE("bounded-inactive-test", bounded_inactive_test) + KAHIP_PARSE_MODE("bounded-inactive-wait", bounded_inactive_wait) + KAHIP_PARSE_MODE("bounded-later-init", bounded_later_init) + KAHIP_PARSE_MODE("bounded-later-test", bounded_later_test) + KAHIP_PARSE_MODE("bounded-later-wait", bounded_later_wait) + KAHIP_PARSE_MODE("send-while-active", send_while_active) + KAHIP_PARSE_MODE("receive-while-active", receive_while_active) + KAHIP_PARSE_MODE("post-finalize-active-request", post_finalize_active_request) + KAHIP_PARSE_MODE("post-finalize-complete-request", + post_finalize_complete_request) + KAHIP_PARSE_MODE("post-finalize-complete-wait", post_finalize_complete_wait) + KAHIP_PARSE_MODE("post-finalize-immediate-context", + post_finalize_immediate_context) + KAHIP_PARSE_MODE("post-finalize-persistent-context", + post_finalize_persistent_context) + KAHIP_PARSE_MODE("initialized-query", initialized_query) + KAHIP_PARSE_MODE("finalized-query", finalized_query) +#undef KAHIP_PARSE_MODE + return false; +} + +[[nodiscard]] auto sends() -> segmented_buffer { + return segmented_buffer::from_segments( + std::vector>{{{7, 0}}}); +} + +void finalize_for_raw_abort() { + auto const result = MPI_Finalize(); + if (result != MPI_SUCCESS) { + std::fprintf(stderr, "MPI_Finalize returned raw error %d\n", result); + std::_Exit(70); + } + runtime_was_finalized = true; +} + +void run_one_shot(distributed_graph const& graph) { + auto request = start_neighbor_all_to_all_v(sends(), graph); + switch (selected_mode) { + case failure_mode::test: + static_cast(request.test()); + return; + case failure_mode::wait: + static_cast(std::move(request).wait()); + return; + case failure_mode::destructor_wait: + return; + case failure_mode::post_finalize_active_request: + while (!underlying_request_completed) { + if (request.test()) { + std::fputs("active request completed visibly before finalization\n", + stderr); + std::_Exit(2); + } + } + finalize_for_raw_abort(); + return; + case failure_mode::post_finalize_complete_request: + while (!request.test()) { + } + finalize_for_raw_abort(); + static_cast(request.test()); + return; + case failure_mode::post_finalize_complete_wait: + while (!request.test()) { + } + finalize_for_raw_abort(); + static_cast(std::move(request).wait()); + return; + default: + return; + } +} + +void run_context(distributed_graph const& graph) { + auto const policy = is_persistent_mode() ? persistence_policy::required + : persistence_policy::disabled; + auto const bounded_mode = + selected_mode == failure_mode::bounded_inactive_test || + selected_mode == failure_mode::bounded_inactive_wait || + selected_mode == failure_mode::bounded_later_init || + selected_mode == failure_mode::bounded_later_test || + selected_mode == failure_mode::bounded_later_wait; + auto const options = + bounded_mode + ? context_options{ + .collective = collective_options{.mpi3_round_ceiling = 2, + .force_mpi3 = true}, + .persistence = persistence_policy::disabled} + : context_options{.persistence = policy}; + neighbor_all_to_all_v_context context{ + graph, {bounded_mode ? std::size_t{5} : std::size_t{1}}, options}; + + switch (selected_mode) { + case failure_mode::persistent_init: + return; + case failure_mode::active_restart: + context.start(); + context.start(); + return; + case failure_mode::inactive_test: + context.start(); + context.wait(); + static_cast(context.test()); + return; + case failure_mode::bounded_inactive_test: + context.start(); + context.wait(); + static_cast(context.test()); + return; + case failure_mode::bounded_inactive_wait: + context.start(); + context.wait(); + context.wait(); + return; + case failure_mode::bounded_later_init: + case failure_mode::bounded_later_wait: + context.start(); + context.wait(); + return; + case failure_mode::bounded_later_test: + context.start(); + while (!context.test()) { + } + return; + case failure_mode::send_while_active: + context.start(); + static_cast(context.send_segment(0)); + return; + case failure_mode::receive_while_active: + context.start(); + static_cast(context.received_segment(0)); + return; + case failure_mode::persistent_start: + context.start(); + return; + case failure_mode::persistent_test: + context.start(); + static_cast(context.test()); + return; + case failure_mode::persistent_wait: + context.start(); + context.wait(); + return; + case failure_mode::request_free: + return; + case failure_mode::post_finalize_immediate_context: + case failure_mode::post_finalize_persistent_context: + context.start(); + context.wait(); + finalize_for_raw_abort(); + return; + case failure_mode::initialized_query: + case failure_mode::finalized_query: + context.start(); + context.wait(); + inject_initialized_query = + selected_mode == failure_mode::initialized_query; + inject_finalized_query = selected_mode == failure_mode::finalized_query; + return; + default: + return; + } +} +} // namespace + +int main(int argc, char* argv[]) { + if (argc != 2 || !parse_mode(argv[1])) { + std::fputs("usage: mpi_async_neighbors_failure_probe MODE\n", stderr); + return 64; + } + if (std::signal(SIGABRT, observed_raw_abort) == SIG_ERR) { + std::fputs("could not install SIGABRT observation handler\n", stderr); + return 70; + } + auto const init_result = MPI_Init(&argc, &argv); + if (init_result != MPI_SUCCESS) { + std::fprintf(stderr, "MPI_Init returned raw error %d\n", init_result); + return 70; + } + + { + communicator_view const world{MPI_COMM_WORLD}; + distributed_graph graph{world, {world.rank()}}; + if (selected_mode == failure_mode::immediate_init || + selected_mode == failure_mode::test || + selected_mode == failure_mode::wait || + selected_mode == failure_mode::destructor_wait || + selected_mode == failure_mode::post_finalize_active_request || + selected_mode == failure_mode::post_finalize_complete_request || + selected_mode == failure_mode::post_finalize_complete_wait) { + run_one_shot(graph); + } else { + run_context(graph); + } + } + + auto const mode = mode_name(); + std::fprintf(stderr, "%.*s failure did not abort\n", + static_cast(mode.size()), mode.data()); + if (!runtime_was_finalized) { + static_cast(MPI_Finalize()); + } + return 2; +} diff --git a/parallel/parallel_src/tests/communication/mpi_async_neighbors_test.cpp b/parallel/parallel_src/tests/communication/mpi_async_neighbors_test.cpp new file mode 100644 index 00000000..00b137dc --- /dev/null +++ b/parallel/parallel_src/tests/communication/mpi_async_neighbors_test.cpp @@ -0,0 +1,1552 @@ +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_adapter.h" +#include "kahip_mpi_capabilities.h" + +namespace async_test_support { +struct wire_entry { + std::uint64_t generation; + int source; + int destination; + + auto operator==(wire_entry const&) const -> bool = default; +}; +} // namespace async_test_support + +template <> +struct parhip::mpi::wire_members { + inline static constexpr auto value = std::tuple{ + &async_test_support::wire_entry::generation, + &async_test_support::wire_entry::source, + &async_test_support::wire_entry::destination}; +}; + +namespace async_protocol_probe { +inline bool active = false; +inline bool force_first_test_incomplete = false; +inline bool first_test_was_forced = false; +inline int count_exchange_calls = 0; +inline int blocking_payload_calls = 0; +inline int blocking_payload_c_calls = 0; +inline int immediate_payload_calls = 0; +inline int immediate_payload_c_calls = 0; +inline int persistent_init_calls = 0; +inline int persistent_init_c_calls = 0; +inline int start_calls = 0; +inline int test_calls = 0; +inline int wait_calls = 0; +inline int waitall_calls = 0; +inline int hidden_completion_calls = 0; +inline int cancel_calls = 0; +inline int request_free_calls = 0; +inline int point_to_point_calls = 0; +inline int tracked_type_free_calls = 0; +inline int tracked_communicator_free_calls = 0; +inline bool tracked_request_active = false; +inline bool request_free_was_inactive = false; +inline MPI_Request tracked_request = MPI_REQUEST_NULL; +inline MPI_Datatype tracked_datatype = MPI_DATATYPE_NULL; +inline MPI_Comm tracked_communicator = MPI_COMM_NULL; +inline std::array lifecycle{}; +inline std::size_t lifecycle_size = 0; +inline bool lifecycle_overflow = false; + +void reset() { + force_first_test_incomplete = false; + first_test_was_forced = false; + count_exchange_calls = 0; + blocking_payload_calls = 0; + blocking_payload_c_calls = 0; + immediate_payload_calls = 0; + immediate_payload_c_calls = 0; + persistent_init_calls = 0; + persistent_init_c_calls = 0; + start_calls = 0; + test_calls = 0; + wait_calls = 0; + waitall_calls = 0; + hidden_completion_calls = 0; + cancel_calls = 0; + request_free_calls = 0; + point_to_point_calls = 0; + tracked_type_free_calls = 0; + tracked_communicator_free_calls = 0; + tracked_request_active = false; + request_free_was_inactive = false; + tracked_request = MPI_REQUEST_NULL; + tracked_datatype = MPI_DATATYPE_NULL; + tracked_communicator = MPI_COMM_NULL; + lifecycle_size = 0; + lifecycle_overflow = false; +} + +void record_event(std::string_view event) noexcept { + if (lifecycle_size == lifecycle.size()) { + lifecycle_overflow = true; + return; + } + lifecycle[lifecycle_size++] = event; +} + +[[nodiscard]] auto events() noexcept -> std::span { + return {lifecycle.data(), lifecycle_size}; +} + +void track_operation(MPI_Comm communicator, + MPI_Datatype datatype, + MPI_Request request, + bool request_is_active) { + tracked_communicator = communicator; + tracked_datatype = datatype; + tracked_request = request; + tracked_request_active = request_is_active; +} +} // namespace async_protocol_probe + +namespace semantic_error_protocol_probe { +inline bool active = false; +inline int error_string_calls = 0; + +class activation final { + public: + activation() noexcept { + error_string_calls = 0; + active = true; + } + ~activation() noexcept { active = false; } + + activation(activation const&) = delete; + auto operator=(activation const&) -> activation& = delete; +}; +} // namespace semantic_error_protocol_probe + +namespace backend_agreement_probe { +inline bool active = false; +inline int band_calls = 0; + +class activation final { + public: + activation() noexcept { + band_calls = 0; + active = true; + } + ~activation() noexcept { active = false; } + + activation(activation const&) = delete; + auto operator=(activation const&) -> activation& = delete; +}; +} // namespace backend_agreement_probe + +extern "C" int MPI_Allreduce(void const* send_buffer, + void* receive_buffer, + int count, + MPI_Datatype datatype, + MPI_Op operation, + MPI_Comm communicator) { + if (backend_agreement_probe::active && count == 2 && + datatype == MPI_UINT64_T && operation == MPI_BAND) { + ++backend_agreement_probe::band_calls; + } + return PMPI_Allreduce(send_buffer, receive_buffer, count, datatype, operation, + communicator); +} + +extern "C" int MPI_Error_string(int error_code, + char* error_text, + int* error_text_length) { + if (semantic_error_protocol_probe::active) { + ++semantic_error_protocol_probe::error_string_calls; + } + return PMPI_Error_string(error_code, error_text, error_text_length); +} + +extern "C" int MPI_Neighbor_alltoall(void const* send_buffer, + int send_count, + MPI_Datatype send_datatype, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (async_protocol_probe::active) { + ++async_protocol_probe::count_exchange_calls; + } + return PMPI_Neighbor_alltoall(send_buffer, send_count, send_datatype, + receive_buffer, receive_count, receive_datatype, + communicator); +} + +extern "C" int MPI_Neighbor_alltoallv(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (async_protocol_probe::active) { + ++async_protocol_probe::blocking_payload_calls; + } + return PMPI_Neighbor_alltoallv(send_buffer, send_counts, send_displacements, + send_datatype, receive_buffer, receive_counts, + receive_displacements, receive_datatype, + communicator); +} + +#if defined(KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C) && \ + KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C +extern "C" int MPI_Neighbor_alltoallv_c(void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (async_protocol_probe::active) { + ++async_protocol_probe::blocking_payload_c_calls; + } + return PMPI_Neighbor_alltoallv_c(send_buffer, send_counts, send_displacements, + send_datatype, receive_buffer, + receive_counts, receive_displacements, + receive_datatype, communicator); +} +#endif + +extern "C" int MPI_Ineighbor_alltoallv(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Request* request) { + if (async_protocol_probe::active) { + ++async_protocol_probe::immediate_payload_calls; + } + auto const result = PMPI_Ineighbor_alltoallv( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator, request); + if (async_protocol_probe::active && result == MPI_SUCCESS) { + async_protocol_probe::track_operation(communicator, send_datatype, *request, + true); + async_protocol_probe::record_event("initiate"); + } + return result; +} + +#if defined(KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV_C) && \ + KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV_C +extern "C" int MPI_Ineighbor_alltoallv_c(void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Request* request) { + if (async_protocol_probe::active) { + ++async_protocol_probe::immediate_payload_c_calls; + } + auto const result = PMPI_Ineighbor_alltoallv_c( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator, request); + if (async_protocol_probe::active && result == MPI_SUCCESS) { + async_protocol_probe::track_operation(communicator, send_datatype, *request, + true); + async_protocol_probe::record_event("initiate-c"); + } + return result; +} +#endif + +#if defined(KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT) && \ + KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT +extern "C" int MPI_Neighbor_alltoallv_init(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Info info, + MPI_Request* request) { + if (async_protocol_probe::active) { + ++async_protocol_probe::persistent_init_calls; + } + auto const result = PMPI_Neighbor_alltoallv_init( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator, info, request); + if (async_protocol_probe::active && result == MPI_SUCCESS) { + async_protocol_probe::track_operation(communicator, send_datatype, *request, + false); + async_protocol_probe::record_event("persistent-init"); + } + return result; +} +#endif + +#if defined(KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT_C) && \ + KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT_C +extern "C" int MPI_Neighbor_alltoallv_init_c( + void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Info info, + MPI_Request* request) { + if (async_protocol_probe::active) { + ++async_protocol_probe::persistent_init_c_calls; + } + auto const result = PMPI_Neighbor_alltoallv_init_c( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator, info, request); + if (async_protocol_probe::active && result == MPI_SUCCESS) { + async_protocol_probe::track_operation(communicator, send_datatype, *request, + false); + async_protocol_probe::record_event("persistent-init-c"); + } + return result; +} +#endif + +extern "C" int MPI_Start(MPI_Request* request) { + auto const tracked = async_protocol_probe::active && + *request == async_protocol_probe::tracked_request; + if (tracked) { + ++async_protocol_probe::start_calls; + async_protocol_probe::record_event("start"); + } + auto const result = PMPI_Start(request); + if (tracked && result == MPI_SUCCESS) { + async_protocol_probe::tracked_request_active = true; + } + return result; +} + +extern "C" int MPI_Test(MPI_Request* request, + int* complete, + MPI_Status* status) { + auto const tracked = async_protocol_probe::active && + *request == async_protocol_probe::tracked_request; + if (tracked) { + ++async_protocol_probe::test_calls; + if (async_protocol_probe::force_first_test_incomplete && + !async_protocol_probe::first_test_was_forced) { + async_protocol_probe::first_test_was_forced = true; + *complete = 0; + return MPI_SUCCESS; + } + } + auto const result = PMPI_Test(request, complete, status); + if (tracked && result == MPI_SUCCESS && *complete != 0) { + async_protocol_probe::tracked_request_active = false; + async_protocol_probe::record_event("test-complete"); + } + return result; +} + +extern "C" int MPI_Wait(MPI_Request* request, MPI_Status* status) { + auto const tracked = async_protocol_probe::active && + *request == async_protocol_probe::tracked_request; + if (tracked) { + ++async_protocol_probe::wait_calls; + async_protocol_probe::record_event("wait"); + } + auto const result = PMPI_Wait(request, status); + if (tracked && result == MPI_SUCCESS) { + async_protocol_probe::tracked_request_active = false; + } + return result; +} + +extern "C" int MPI_Waitall(int count, + MPI_Request requests[], + MPI_Status statuses[]) { + if (async_protocol_probe::active) { + ++async_protocol_probe::waitall_calls; + } + return PMPI_Waitall(count, requests, statuses); +} + +extern "C" int MPI_Waitany(int count, + MPI_Request requests[], + int* index, + MPI_Status* status) { + if (async_protocol_probe::active) { + ++async_protocol_probe::hidden_completion_calls; + } + return PMPI_Waitany(count, requests, index, status); +} + +extern "C" int MPI_Waitsome(int count, + MPI_Request requests[], + int* completed_count, + int indices[], + MPI_Status statuses[]) { + if (async_protocol_probe::active) { + ++async_protocol_probe::hidden_completion_calls; + } + return PMPI_Waitsome(count, requests, completed_count, indices, statuses); +} + +extern "C" int MPI_Testall(int count, + MPI_Request requests[], + int* complete, + MPI_Status statuses[]) { + if (async_protocol_probe::active) { + ++async_protocol_probe::hidden_completion_calls; + } + return PMPI_Testall(count, requests, complete, statuses); +} + +extern "C" int MPI_Testany(int count, + MPI_Request requests[], + int* index, + int* complete, + MPI_Status* status) { + if (async_protocol_probe::active) { + ++async_protocol_probe::hidden_completion_calls; + } + return PMPI_Testany(count, requests, index, complete, status); +} + +extern "C" int MPI_Testsome(int count, + MPI_Request requests[], + int* completed_count, + int indices[], + MPI_Status statuses[]) { + if (async_protocol_probe::active) { + ++async_protocol_probe::hidden_completion_calls; + } + return PMPI_Testsome(count, requests, completed_count, indices, statuses); +} + +extern "C" int MPI_Cancel(MPI_Request* request) { + if (async_protocol_probe::active) { + ++async_protocol_probe::cancel_calls; + } + return PMPI_Cancel(request); +} + +extern "C" int MPI_Request_free(MPI_Request* request) { + auto const tracked = async_protocol_probe::active && + *request == async_protocol_probe::tracked_request; + if (tracked) { + ++async_protocol_probe::request_free_calls; + async_protocol_probe::request_free_was_inactive = + !async_protocol_probe::tracked_request_active; + async_protocol_probe::record_event("request-free"); + } + return PMPI_Request_free(request); +} + +extern "C" int MPI_Type_free(MPI_Datatype* datatype) { + auto const tracked = async_protocol_probe::active && + *datatype == async_protocol_probe::tracked_datatype; + if (tracked) { + ++async_protocol_probe::tracked_type_free_calls; + async_protocol_probe::record_event("type-free"); + } + return PMPI_Type_free(datatype); +} + +extern "C" int MPI_Comm_free(MPI_Comm* communicator) { + auto const tracked = + async_protocol_probe::active && + *communicator == async_protocol_probe::tracked_communicator; + if (tracked) { + ++async_protocol_probe::tracked_communicator_free_calls; + async_protocol_probe::record_event("comm-free"); + } + return PMPI_Comm_free(communicator); +} + +#define KAHIP_COUNT_P2P_WRAPPER(name, signature, call) \ + extern "C" int name signature { \ + if (async_protocol_probe::active) { \ + ++async_protocol_probe::point_to_point_calls; \ + } \ + return call; \ + } + +KAHIP_COUNT_P2P_WRAPPER(MPI_Isend, + (void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator, + MPI_Request* request), + PMPI_Isend(buffer, + count, + datatype, + destination, + tag, + communicator, + request)) +KAHIP_COUNT_P2P_WRAPPER( + MPI_Irecv, + (void* buffer, + int count, + MPI_Datatype datatype, + int source, + int tag, + MPI_Comm communicator, + MPI_Request* request), + PMPI_Irecv(buffer, count, datatype, source, tag, communicator, request)) +KAHIP_COUNT_P2P_WRAPPER( + MPI_Send, + (void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator), + PMPI_Send(buffer, count, datatype, destination, tag, communicator)) +KAHIP_COUNT_P2P_WRAPPER( + MPI_Recv, + (void* buffer, + int count, + MPI_Datatype datatype, + int source, + int tag, + MPI_Comm communicator, + MPI_Status* status), + PMPI_Recv(buffer, count, datatype, source, tag, communicator, status)) +KAHIP_COUNT_P2P_WRAPPER( + MPI_Probe, + (int source, int tag, MPI_Comm communicator, MPI_Status* status), + PMPI_Probe(source, tag, communicator, status)) +KAHIP_COUNT_P2P_WRAPPER( + MPI_Iprobe, + (int source, int tag, MPI_Comm communicator, int* flag, MPI_Status* status), + PMPI_Iprobe(source, tag, communicator, flag, status)) +KAHIP_COUNT_P2P_WRAPPER(MPI_Mprobe, + (int source, + int tag, + MPI_Comm communicator, + MPI_Message* message, + MPI_Status* status), + PMPI_Mprobe(source, tag, communicator, message, status)) +KAHIP_COUNT_P2P_WRAPPER( + MPI_Improbe, + (int source, + int tag, + MPI_Comm communicator, + int* flag, + MPI_Message* message, + MPI_Status* status), + PMPI_Improbe(source, tag, communicator, flag, message, status)) +KAHIP_COUNT_P2P_WRAPPER(MPI_Sendrecv, + (void const* send_buffer, + int send_count, + MPI_Datatype send_datatype, + int destination, + int send_tag, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_datatype, + int source, + int receive_tag, + MPI_Comm communicator, + MPI_Status* status), + PMPI_Sendrecv(send_buffer, + send_count, + send_datatype, + destination, + send_tag, + receive_buffer, + receive_count, + receive_datatype, + source, + receive_tag, + communicator, + status)) +KAHIP_COUNT_P2P_WRAPPER(MPI_Sendrecv_replace, + (void* buffer, + int count, + MPI_Datatype datatype, + int destination, + int send_tag, + int source, + int receive_tag, + MPI_Comm communicator, + MPI_Status* status), + PMPI_Sendrecv_replace(buffer, + count, + datatype, + destination, + send_tag, + source, + receive_tag, + communicator, + status)) + +#undef KAHIP_COUNT_P2P_WRAPPER + +namespace { +using async_test_support::wire_entry; +using parhip::mpi::collective_options; +using parhip::mpi::communicator_view; +using parhip::mpi::context_options; +using parhip::mpi::distributed_graph; +using parhip::mpi::neighbor_all_to_all_v; +using parhip::mpi::neighbor_all_to_all_v_context; +using parhip::mpi::neighbor_exchange_request; +using parhip::mpi::persistence_policy; +using parhip::mpi::segmented_buffer; +using parhip::mpi::start_neighbor_all_to_all_v; + +static_assert(!std::is_nothrow_constructible_v< + parhip::mpi::detail::direct_neighbor_storage, + parhip::mpi::communicator, parhip::mpi::datatype, + parhip::mpi::segmented_buffer, + parhip::mpi::segmented_buffer, + parhip::mpi::detail::direct_neighbor_layout, + parhip::mpi::detail::neighbor_direct_backend, + std::optional>, + "allocating direct-neighbor storage must propagate allocation " + "failures to the communicator-scoped fail-fast boundary"); + +template +void require_exact_common_mpi_error(Operation&& operation, + std::string_view expected_context, + communicator_view communicator) { + auto caught = 0; + auto exact_dynamic_type = 0; + auto raw_code_matches = 0; + auto context_matches = 0; + auto error_string_calls = 0; + { + semantic_error_protocol_probe::activation observation{}; + try { + std::invoke(std::forward(operation)); + } catch (parhip::mpi::mpi_error const& error) { + caught = 1; + exact_dynamic_type = + typeid(error) == typeid(parhip::mpi::mpi_error) ? 1 : 0; + raw_code_matches = error.error_code() == MPI_ERR_ARG ? 1 : 0; + context_matches = error.context() == expected_context ? 1 : 0; + } catch (...) { + caught = 1; + } + error_string_calls = semantic_error_protocol_probe::error_string_calls; + } + auto local = std::array{caught, exact_dynamic_type, raw_code_matches, + context_matches, error_string_calls}; + auto global = std::array{0, 0, 0, 0, 0}; + REQUIRE(PMPI_Allreduce(local.data(), global.data(), + static_cast(local.size()), MPI_INT, MPI_SUM, + communicator.native_handle()) == MPI_SUCCESS); + REQUIRE(global == std::array{communicator.size(), communicator.size(), + communicator.size(), communicator.size(), 0}); +} + +template +void require_collective_semantic_error(Operation&& operation, + std::string_view expected_context, + communicator_view communicator) { + require_exact_common_mpi_error(std::forward(operation), + expected_context, communicator); +} + +auto ring_segments(distributed_graph const& graph, + int rank, + std::uint64_t generation) + -> std::vector> { + auto segments = std::vector>{}; + segments.reserve(graph.destinations().size()); + for (auto const destination : graph.destinations()) { + auto const count = static_cast((rank + destination) % 3 + 1); + segments.emplace_back(count, wire_entry{generation, rank, destination}); + } + return segments; +} + +void require_default_immediate_backend(int expected_calls) { +#if KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV_C + REQUIRE(async_protocol_probe::immediate_payload_calls == 0); + REQUIRE(async_protocol_probe::immediate_payload_c_calls == expected_calls); +#else + REQUIRE(async_protocol_probe::immediate_payload_calls == expected_calls); + REQUIRE(async_protocol_probe::immediate_payload_c_calls == 0); +#endif +} + +void require_default_persistent_backend(int expected_calls) { +#if KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT_C + REQUIRE(async_protocol_probe::persistent_init_calls == 0); + REQUIRE(async_protocol_probe::persistent_init_c_calls == expected_calls); +#else + REQUIRE(async_protocol_probe::persistent_init_calls == expected_calls); + REQUIRE(async_protocol_probe::persistent_init_c_calls == 0); +#endif +} + +TEST_CASE("Task 7B MPI capabilities match independent generated probes", + "[unit][mpi][neighbor][async][capability]") { +#if !defined(KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV) || \ + !defined(KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV_C) || \ + !defined(KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT) || \ + !defined(KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT_C) + FAIL("generated Task 7B MPI capability macro is missing"); +#else + STATIC_REQUIRE(parhip::mpi::capabilities::has_ineighbor_alltoallv == + (KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV != 0)); + STATIC_REQUIRE(parhip::mpi::capabilities::has_ineighbor_alltoallv_c == + (KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV_C != 0)); + STATIC_REQUIRE(parhip::mpi::capabilities::has_neighbor_alltoallv_init == + (KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT != 0)); + STATIC_REQUIRE(parhip::mpi::capabilities::has_neighbor_alltoallv_init_c == + (KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT_C != 0)); + STATIC_REQUIRE(parhip::mpi::capabilities::has_ineighbor_alltoallv); +#endif +} + +TEST_CASE("one-shot neighborhood request is move-only and context immovable", + "[unit][mpi][neighbor][async][ownership]") { + STATIC_REQUIRE_FALSE( + std::is_copy_constructible_v>); + STATIC_REQUIRE(std::is_move_constructible_v>); + STATIC_REQUIRE_FALSE( + std::is_move_assignable_v>); + STATIC_REQUIRE_FALSE( + std::is_copy_constructible_v>); + STATIC_REQUIRE_FALSE( + std::is_move_constructible_v>); +} + +TEST_CASE("one-shot zero-degree initiation is immediate and owns completion", + "[unit][mpi][neighbor][async][zero]") { + communicator_view const world{MPI_COMM_WORLD}; + distributed_graph graph{world, {}}; + auto sends = + segmented_buffer::from_segments(std::vector>{}); + + async_protocol_probe::reset(); + async_protocol_probe::active = true; + auto request = start_neighbor_all_to_all_v(std::move(sends), graph); + REQUIRE(async_protocol_probe::count_exchange_calls == 1); + require_default_immediate_backend(1); + REQUIRE(async_protocol_probe::blocking_payload_calls == 0); + REQUIRE(async_protocol_probe::blocking_payload_c_calls == 0); + REQUIRE(async_protocol_probe::test_calls == 0); + REQUIRE(async_protocol_probe::wait_calls == 0); + REQUIRE(async_protocol_probe::waitall_calls == 0); + REQUIRE(async_protocol_probe::hidden_completion_calls == 0); + REQUIRE(async_protocol_probe::point_to_point_calls == 0); + auto received = std::move(request).wait(); + async_protocol_probe::active = false; + + REQUIRE(received.storage().empty()); + REQUIRE(received.segment_count() == 0); + REQUIRE(async_protocol_probe::wait_calls == 1); + REQUIRE(async_protocol_probe::point_to_point_calls == 0); + REQUIRE(async_protocol_probe::waitall_calls == 0); + REQUIRE(async_protocol_probe::hidden_completion_calls == 0); + REQUIRE(async_protocol_probe::cancel_calls == 0); +} + +TEST_CASE("one-shot preserves an explicit empty destination segment", + "[unit][mpi][neighbor][async][empty]") { + communicator_view const world{MPI_COMM_WORLD}; + distributed_graph graph{world, {world.rank()}}; + auto sends = + segmented_buffer::from_segments(std::vector>{{}}); + + async_protocol_probe::reset(); + async_protocol_probe::active = true; + auto request = start_neighbor_all_to_all_v(std::move(sends), graph); + auto received = std::move(request).wait(); + async_protocol_probe::active = false; + + REQUIRE(received.segment_count() == 1); + REQUIRE(received.segment(0).empty()); + REQUIRE(async_protocol_probe::count_exchange_calls == 1); + require_default_immediate_backend(1); + REQUIRE(async_protocol_probe::point_to_point_calls == 0); + REQUIRE(async_protocol_probe::hidden_completion_calls == 0); + REQUIRE(async_protocol_probe::cancel_calls == 0); +} + +TEST_CASE("active one-shot request survives moves and source graph destruction", + "[unit][mpi][neighbor][async][move][order][wire]") { + communicator_view const world{MPI_COMM_WORLD}; + auto const rank = world.rank(); + auto const next = (rank + 1) % world.size(); + auto sources = std::vector{}; + auto received = std::optional>{}; + async_protocol_probe::reset(); + async_protocol_probe::force_first_test_incomplete = true; + async_protocol_probe::active = true; + { + auto request = [&] { + distributed_graph graph{world, {next}}; + sources.assign(graph.sources().begin(), graph.sources().end()); + auto started = start_neighbor_all_to_all_v( + segmented_buffer::from_segments( + ring_segments(graph, rank, 17)), + graph); + REQUIRE(MPI_Barrier(world.native_handle()) == MPI_SUCCESS); + return neighbor_exchange_request{std::move(started)}; + }(); + + neighbor_exchange_request moved{std::move(request)}; + REQUIRE_FALSE(moved.test()); + auto completion_observed = false; + for (int attempt = 0; attempt < 10'000 && !completion_observed; ++attempt) { + completion_observed = moved.test(); + } + REQUIRE(completion_observed); + received.emplace(std::move(moved).wait()); + } + async_protocol_probe::active = false; + + REQUIRE(async_protocol_probe::first_test_was_forced); + REQUIRE(async_protocol_probe::count_exchange_calls == 1); + require_default_immediate_backend(1); + REQUIRE(async_protocol_probe::wait_calls == 0); + REQUIRE(async_protocol_probe::request_free_calls == 0); + REQUIRE(async_protocol_probe::tracked_type_free_calls == 1); + REQUIRE(async_protocol_probe::tracked_communicator_free_calls == 1); + REQUIRE(async_protocol_probe::point_to_point_calls == 0); + REQUIRE(received->segment_count() == sources.size()); + for (std::size_t index = 0; index < sources.size(); ++index) { + REQUIRE(std::ranges::equal( + received->segment(index), + std::vector( + static_cast((sources[index] + rank) % 3 + 1), + wire_entry{17, sources[index], rank}))); + } +} + +TEST_CASE("one-shot MPI-3 fallback advances deterministic bounded rounds", + "[unit][mpi][neighbor][async][bounded]") { + communicator_view const world{MPI_COMM_WORLD}; + distributed_graph graph{world, {world.rank()}}; + auto const element_count = + world.rank() == 0 ? std::size_t{5} : std::size_t{1}; + auto const segments = std::vector>{ + std::vector(element_count, world.rank())}; + auto const options = + collective_options{.mpi3_round_ceiling = 2, .force_mpi3 = true}; + + async_protocol_probe::reset(); + async_protocol_probe::force_first_test_incomplete = true; + async_protocol_probe::active = true; + auto request = start_neighbor_all_to_all_v( + segmented_buffer::from_segments(segments), graph, options); + REQUIRE(async_protocol_probe::count_exchange_calls == 1); + REQUIRE(async_protocol_probe::immediate_payload_calls == 1); + REQUIRE(async_protocol_probe::immediate_payload_c_calls == 0); + REQUIRE(async_protocol_probe::blocking_payload_calls == 0); + REQUIRE_FALSE(request.test()); + auto complete = false; + for (int attempt = 0; attempt < 10'000 && !complete; ++attempt) { + complete = request.test(); + } + REQUIRE(complete); + auto received = std::move(request).wait(); + async_protocol_probe::active = false; + REQUIRE(std::ranges::equal(received.segment(0), segments[0])); + REQUIRE(async_protocol_probe::immediate_payload_calls == 3); + REQUIRE(async_protocol_probe::first_test_was_forced); + REQUIRE(async_protocol_probe::test_calls >= 4); + REQUIRE(async_protocol_probe::wait_calls == 0); + REQUIRE(async_protocol_probe::point_to_point_calls == 0); +} + +TEST_CASE("bounded MPI-3 star phases retain zero-count participants", + "[unit][mpi][neighbor][async][bounded][asymmetric][sparse]") { + communicator_view const world{MPI_COMM_WORLD}; + if (world.size() < 2) { + return; + } + + auto outgoing = std::vector{}; + if (world.rank() == 0) { + outgoing.resize(static_cast(world.size() - 1)); + std::ranges::iota(outgoing, 1); + } + distributed_graph graph{world, std::move(outgoing)}; + auto const counts = std::vector(graph.destinations().size(), 5); + + async_protocol_probe::reset(); + async_protocol_probe::active = true; + { + neighbor_all_to_all_v_context context{ + graph, + counts, + context_options{ + .collective = collective_options{.mpi3_round_ceiling = 2, + .force_mpi3 = true}}}; + for (std::size_t index = 0; index < graph.destinations().size(); ++index) { + std::ranges::fill(context.send_segment(index), + graph.destinations()[index]); + } + context.start(); + context.wait(); + if (world.rank() == 0) { + REQUIRE(graph.sources().empty()); + } else { + REQUIRE(std::ranges::equal(graph.sources(), std::array{0})); + REQUIRE(std::ranges::equal( + context.received_segment(0), + std::vector(5, world.rank()))); + } + } + async_protocol_probe::active = false; + + REQUIRE(async_protocol_probe::immediate_payload_calls == + 3 * (world.size() - 1)); + REQUIRE(async_protocol_probe::immediate_payload_c_calls == 0); + REQUIRE(async_protocol_probe::point_to_point_calls == 0); +} + +TEST_CASE("one-shot options are validated collectively before count exchange", + "[unit][mpi][neighbor][async][options][failure]") { + communicator_view const world{MPI_COMM_WORLD}; + distributed_graph graph{world, {world.rank()}}; + + for (auto const options : std::array{ + collective_options{.mpi3_round_ceiling = 0, .force_mpi3 = true}, + collective_options{.mpi3_round_ceiling = 2, + .force_mpi3 = world.rank() == 0}}) { + if (world.size() == 1 && options.mpi3_round_ceiling != 0) { + continue; + } + async_protocol_probe::reset(); + async_protocol_probe::active = true; + require_collective_semantic_error( + [&] { + static_cast(start_neighbor_all_to_all_v( + segmented_buffer::from_segments( + std::vector>{{world.rank()}}), + graph, options)); + }, + "direct neighborhood exchange options must agree collectively", world); + REQUIRE(async_protocol_probe::count_exchange_calls == 0); + REQUIRE(async_protocol_probe::immediate_payload_calls == 0); + REQUIRE(async_protocol_probe::immediate_payload_c_calls == 0); + async_protocol_probe::active = false; + } + + if (world.size() == 1) { + async_protocol_probe::reset(); + async_protocol_probe::active = true; + semantic_error_protocol_probe::activation observation{}; + auto request = start_neighbor_all_to_all_v( + segmented_buffer::from_segments( + std::vector>{{world.rank()}}), + graph, collective_options{.mpi3_round_ceiling = 2, .force_mpi3 = true}); + auto received = std::move(request).wait(); + async_protocol_probe::active = false; + REQUIRE(std::ranges::equal(received.segment(0), std::array{world.rank()})); + REQUIRE(semantic_error_protocol_probe::error_string_calls == 0); + } +} + +TEST_CASE("one-shot rejects a rank-local malformed segment layout commonly", + "[unit][mpi][neighbor][async][layout][failure]") { + communicator_view const world{MPI_COMM_WORLD}; + distributed_graph graph{world, {world.rank()}}; + auto sends = world.rank() == 0 + ? segmented_buffer::uninitialized(0, {}, {}) + : segmented_buffer::from_segments( + std::vector>{{world.rank()}}); + + async_protocol_probe::reset(); + async_protocol_probe::active = true; + require_collective_semantic_error( + [&] { + static_cast(start_neighbor_all_to_all_v(std::move(sends), graph)); + }, + "direct neighborhood exchange input validation failed", world); + REQUIRE(async_protocol_probe::count_exchange_calls == 0); + REQUIRE(async_protocol_probe::immediate_payload_calls == 0); + REQUIRE(async_protocol_probe::immediate_payload_c_calls == 0); + async_protocol_probe::active = false; +} + +TEST_CASE("reusable persistence policy is collectively identical", + "[unit][mpi][neighbor][async][context][options][failure]") { + communicator_view const world{MPI_COMM_WORLD}; + if (world.size() == 1) { + distributed_graph graph{world, {world.rank()}}; + async_protocol_probe::reset(); + async_protocol_probe::active = true; + semantic_error_protocol_probe::activation observation{}; + neighbor_all_to_all_v_context context{ + graph, + {1}, + context_options{.persistence = persistence_policy::disabled}}; + async_protocol_probe::active = false; + REQUIRE(context.send_segment(0).size() == 1); + REQUIRE(semantic_error_protocol_probe::error_string_calls == 0); + return; + } + distributed_graph graph{world, {world.rank()}}; + auto const policy = world.rank() == 0 ? persistence_policy::disabled + : persistence_policy::prefer; + + async_protocol_probe::reset(); + async_protocol_probe::active = true; + require_collective_semantic_error( + [&] { + neighbor_all_to_all_v_context context{ + graph, {1}, context_options{.persistence = policy}}; + static_cast(context); + }, + "direct neighborhood exchange options must agree collectively", world); + REQUIRE(async_protocol_probe::count_exchange_calls == 0); + REQUIRE(async_protocol_probe::immediate_payload_calls == 0); + REQUIRE(async_protocol_probe::persistent_init_calls == 0); + async_protocol_probe::active = false; +} + +TEST_CASE("reusable context rejects fixed send-count cardinality collectively", + "[unit][mpi][neighbor][async][context][layout][failure]") { + communicator_view const world{MPI_COMM_WORLD}; + distributed_graph graph{world, {world.rank()}}; + auto counts = world.rank() == 0 ? std::vector{} + : std::vector{1}; + + async_protocol_probe::reset(); + async_protocol_probe::active = true; + require_collective_semantic_error( + [&] { + neighbor_all_to_all_v_context context{graph, std::move(counts)}; + static_cast(context); + }, + "fixed neighborhood send layout validation failed", world); + async_protocol_probe::active = false; + REQUIRE(async_protocol_probe::count_exchange_calls == 0); + REQUIRE(async_protocol_probe::immediate_payload_calls == 0); + REQUIRE(async_protocol_probe::persistent_init_calls == 0); +} + +TEST_CASE("reusable persistence policy rejects an invalid value collectively", + "[unit][mpi][neighbor][async][context][options][failure]") { + communicator_view const world{MPI_COMM_WORLD}; + distributed_graph graph{world, {world.rank()}}; + + async_protocol_probe::reset(); + async_protocol_probe::active = true; + require_collective_semantic_error( + [&] { + neighbor_all_to_all_v_context context{ + graph, + {1}, + context_options{.persistence = + static_cast(0xff)}}; + static_cast(context); + }, + "direct neighborhood exchange options must agree collectively", world); + async_protocol_probe::active = false; + REQUIRE(async_protocol_probe::count_exchange_calls == 0); + REQUIRE(async_protocol_probe::immediate_payload_calls == 0); + REQUIRE(async_protocol_probe::persistent_init_calls == 0); +} + +TEST_CASE("default reusable context exchanges three fresh generations", + "[unit][mpi][neighbor][async][context][generation]") { + communicator_view const world{MPI_COMM_WORLD}; + auto const rank = world.rank(); + auto const next = (rank + 1) % world.size(); + distributed_graph graph{world, {next}}; + auto const send_counts = std::vector{2}; + + async_protocol_probe::reset(); + async_protocol_probe::active = true; + { + neighbor_all_to_all_v_context context{graph, send_counts}; + REQUIRE(async_protocol_probe::count_exchange_calls == 1); + REQUIRE(async_protocol_probe::persistent_init_calls == 0); + REQUIRE(async_protocol_probe::persistent_init_c_calls == 0); + auto const send_address = context.send_segment(0).data(); + auto receive_address = static_cast(nullptr); + + for (std::uint64_t generation = 1; generation <= 3; ++generation) { + auto send = context.send_segment(0); + REQUIRE(send.data() == send_address); + std::ranges::fill(send, wire_entry{generation, rank, next}); + context.start(); + if (generation == 1) { + async_protocol_probe::force_first_test_incomplete = true; + REQUIRE_FALSE(context.test()); + context.wait(); + } else if (generation == 2) { + auto complete = false; + for (int attempt = 0; attempt < 10'000 && !complete; ++attempt) { + complete = context.test(); + } + REQUIRE(complete); + } else { + context.wait(); + } + REQUIRE(context.send_segment(0).data() == send_address); + REQUIRE(context.received_segment(0).size() == 2); + if (receive_address == nullptr) { + receive_address = context.received_segment(0).data(); + } + REQUIRE(context.received_segment(0).data() == receive_address); + REQUIRE(std::ranges::all_of( + context.received_segment(0), [&](wire_entry const& entry) { + return entry == wire_entry{generation, graph.sources()[0], rank}; + })); + } + } + async_protocol_probe::active = false; + + REQUIRE(async_protocol_probe::count_exchange_calls == 1); + require_default_immediate_backend(3); + REQUIRE(async_protocol_probe::blocking_payload_calls == 0); + REQUIRE(async_protocol_probe::blocking_payload_c_calls == 0); + REQUIRE(async_protocol_probe::request_free_calls == 0); + REQUIRE(async_protocol_probe::tracked_type_free_calls == 1); + REQUIRE(async_protocol_probe::tracked_communicator_free_calls == 1); + REQUIRE(async_protocol_probe::point_to_point_calls == 0); + REQUIRE(async_protocol_probe::waitall_calls == 0); + REQUIRE(async_protocol_probe::hidden_completion_calls == 0); + REQUIRE(async_protocol_probe::cancel_calls == 0); +} + +TEST_CASE("active one-shot destructor waits before owned MPI cleanup", + "[unit][mpi][neighbor][async][destructor][order]") { + communicator_view const world{MPI_COMM_WORLD}; + + async_protocol_probe::reset(); + async_protocol_probe::active = true; + { + distributed_graph graph{world, {world.rank()}}; + { + auto request = start_neighbor_all_to_all_v( + segmented_buffer::from_segments( + std::vector>{ + {{1, world.rank(), world.rank()}}}), + graph); + static_cast(request); + REQUIRE(async_protocol_probe::wait_calls == 0); + } + REQUIRE(async_protocol_probe::wait_calls == 1); + REQUIRE(async_protocol_probe::tracked_type_free_calls == 1); + REQUIRE(async_protocol_probe::tracked_communicator_free_calls == 1); + } + async_protocol_probe::active = false; + + REQUIRE_FALSE(async_protocol_probe::lifecycle_overflow); + auto const lifecycle = async_protocol_probe::events(); + auto const wait = std::ranges::find(lifecycle, "wait"); + auto const type_free = std::ranges::find(lifecycle, "type-free"); + auto const comm_free = std::ranges::find(lifecycle, "comm-free"); + REQUIRE(wait < type_free); + REQUIRE(type_free < comm_free); + REQUIRE(async_protocol_probe::request_free_calls == 0); +} + +TEST_CASE("active reusable destructor completes before releasing resources", + "[unit][mpi][neighbor][async][context][destructor][order]") { + communicator_view const world{MPI_COMM_WORLD}; + distributed_graph graph{world, {world.rank()}}; + auto policies = std::vector{persistence_policy::disabled}; +#if (defined(KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT) && \ + KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT) || \ + (defined(KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT_C) && \ + KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT_C) + policies.push_back(persistence_policy::required); +#endif + + for (auto const policy : policies) { + async_protocol_probe::reset(); + async_protocol_probe::active = true; + { + neighbor_all_to_all_v_context context{ + graph, {1}, context_options{.persistence = policy}}; + context.send_segment(0)[0] = wire_entry{1, world.rank(), world.rank()}; + context.start(); + REQUIRE(async_protocol_probe::wait_calls == 0); + } + async_protocol_probe::active = false; + + REQUIRE_FALSE(async_protocol_probe::lifecycle_overflow); + auto const lifecycle = async_protocol_probe::events(); + auto const wait = std::ranges::find(lifecycle, "wait"); + auto const type_free = std::ranges::find(lifecycle, "type-free"); + auto const comm_free = std::ranges::find(lifecycle, "comm-free"); + REQUIRE(wait < type_free); + REQUIRE(type_free < comm_free); + if (policy == persistence_policy::required) { + auto const request_free = std::ranges::find(lifecycle, "request-free"); + REQUIRE(wait < request_free); + REQUIRE(request_free < type_free); + REQUIRE(async_protocol_probe::request_free_calls == 1); + REQUIRE(async_protocol_probe::request_free_was_inactive); + } else { + REQUIRE(async_protocol_probe::request_free_calls == 0); + } + } +} + +TEST_CASE("reusable context supports both asymmetric star orientations", + "[unit][mpi][neighbor][async][context][asymmetric]") { + communicator_view const world{MPI_COMM_WORLD}; + if (world.size() < 2) { + return; + } + + for (auto const reversed : std::array{false, true}) { + auto outgoing = std::vector{}; + if ((!reversed && world.rank() == 0) || (reversed && world.rank() != 0)) { + for (int destination = 1; !reversed && destination < world.size(); + ++destination) { + outgoing.push_back(destination); + } + if (reversed) { + outgoing.push_back(0); + } + } + distributed_graph graph{world, std::move(outgoing)}; + auto counts = std::vector(graph.destinations().size(), 1); + neighbor_all_to_all_v_context context{graph, std::move(counts)}; + for (std::size_t index = 0; index < graph.destinations().size(); ++index) { + context.send_segment(index)[0] = + world.rank() * 100 + graph.destinations()[index]; + } + context.start(); + context.wait(); + for (std::size_t index = 0; index < graph.sources().size(); ++index) { + REQUIRE(std::ranges::equal( + context.received_segment(index), + std::array{graph.sources()[index] * 100 + world.rank()})); + } + } +} + +TEST_CASE("persistent context uses guarded init start wait and inactive free", + "[unit][mpi][neighbor][async][context][persistent]") { + communicator_view const world{MPI_COMM_WORLD}; + distributed_graph graph{world, {world.rank()}}; + +#if (defined(KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT) && \ + KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT) || \ + (defined(KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT_C) && \ + KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT_C) + async_protocol_probe::reset(); + async_protocol_probe::active = true; + { + neighbor_all_to_all_v_context context{ + graph, + {1}, + context_options{.persistence = persistence_policy::required}}; + require_default_persistent_backend(1); + for (std::uint64_t generation = 1; generation <= 3; ++generation) { + context.send_segment(0)[0] = + wire_entry{generation, world.rank(), world.rank()}; + context.start(); + context.wait(); + REQUIRE(context.received_segment(0)[0].generation == generation); + } + REQUIRE(async_protocol_probe::request_free_calls == 0); + } + async_protocol_probe::active = false; + REQUIRE(async_protocol_probe::start_calls == 3); + REQUIRE(async_protocol_probe::wait_calls == 3); + REQUIRE(async_protocol_probe::request_free_calls == 1); + REQUIRE(async_protocol_probe::request_free_was_inactive); + REQUIRE(async_protocol_probe::tracked_type_free_calls == 1); + REQUIRE(async_protocol_probe::tracked_communicator_free_calls == 1); + REQUIRE_FALSE(async_protocol_probe::lifecycle_overflow); + auto const lifecycle = async_protocol_probe::events(); + auto const request_free = std::ranges::find(lifecycle, "request-free"); + auto const type_free = std::ranges::find(lifecycle, "type-free"); + auto const comm_free = std::ranges::find(lifecycle, "comm-free"); + REQUIRE(request_free < type_free); + REQUIRE(type_free < comm_free); +#else + require_collective_semantic_error( + [&] { + neighbor_all_to_all_v_context context{ + graph, + {1}, + context_options{.persistence = persistence_policy::required}}; + static_cast(context); + }, + "persistent neighborhood exchange is unavailable", world); +#endif +} + +TEST_CASE("forced MPI-3 disables every MPI-4 persistent backend", + "[unit][mpi][neighbor][async][context][persistent][mpi3]") { + communicator_view const world{MPI_COMM_WORLD}; + distributed_graph graph{world, {world.rank()}}; + auto const mpi3_options = collective_options{.force_mpi3 = true}; + + async_protocol_probe::reset(); + async_protocol_probe::active = true; + { + neighbor_all_to_all_v_context context{ + graph, + {1}, + context_options{.collective = mpi3_options, + .persistence = persistence_policy::prefer}}; + context.send_segment(0)[0] = world.rank(); + context.start(); + context.wait(); + } + REQUIRE(async_protocol_probe::persistent_init_calls == 0); + REQUIRE(async_protocol_probe::persistent_init_c_calls == 0); + REQUIRE(async_protocol_probe::immediate_payload_calls == 1); + REQUIRE(async_protocol_probe::immediate_payload_c_calls == 0); + + require_collective_semantic_error( + [&] { + neighbor_all_to_all_v_context context{ + graph, + {1}, + context_options{.collective = mpi3_options, + .persistence = persistence_policy::required}}; + static_cast(context); + }, + "persistent neighborhood exchange is unavailable", world); + async_protocol_probe::active = false; +} + +TEST_CASE("reusable context restarts the bounded MPI-3 state machine", + "[unit][mpi][neighbor][async][context][bounded]") { + communicator_view const world{MPI_COMM_WORLD}; + distributed_graph graph{world, {world.rank()}}; + async_protocol_probe::reset(); + async_protocol_probe::active = true; + { + neighbor_all_to_all_v_context context{ + graph, + {5}, + context_options{ + .collective = collective_options{.mpi3_round_ceiling = 2, + .force_mpi3 = true}, + .persistence = persistence_policy::prefer}}; + for (auto generation : std::array{7, 11}) { + std::ranges::fill(context.send_segment(0), world.rank() + generation); + context.start(); + context.wait(); + REQUIRE(std::ranges::equal( + context.received_segment(0), + std::vector(5, world.rank() + generation))); + } + } + async_protocol_probe::active = false; + REQUIRE(async_protocol_probe::count_exchange_calls == 1); + REQUIRE(async_protocol_probe::immediate_payload_calls == 6); + REQUIRE(async_protocol_probe::immediate_payload_c_calls == 0); + REQUIRE(async_protocol_probe::persistent_init_calls == 0); + REQUIRE(async_protocol_probe::persistent_init_c_calls == 0); + REQUIRE(async_protocol_probe::wait_calls == 6); + REQUIRE(async_protocol_probe::point_to_point_calls == 0); +} + +TEST_CASE("bounded context destruction drains every active round", + "[unit][mpi][neighbor][async][context][bounded][destructor]") { + communicator_view const world{MPI_COMM_WORLD}; + distributed_graph graph{world, {world.rank()}}; + async_protocol_probe::reset(); + async_protocol_probe::active = true; + { + neighbor_all_to_all_v_context context{ + graph, + {5}, + context_options{ + .collective = collective_options{.mpi3_round_ceiling = 2, + .force_mpi3 = true}}}; + std::ranges::fill( + context.send_segment(0), + wire_entry{0, world.rank(), world.rank()}); + context.start(); + } + async_protocol_probe::active = false; + + REQUIRE(async_protocol_probe::immediate_payload_calls == 3); + REQUIRE(async_protocol_probe::immediate_payload_c_calls == 0); + REQUIRE(async_protocol_probe::wait_calls == 3); + REQUIRE(async_protocol_probe::tracked_type_free_calls == 1); + REQUIRE(async_protocol_probe::tracked_communicator_free_calls == 1); + REQUIRE(async_protocol_probe::point_to_point_calls == 0); +} + +TEST_CASE("required persistence rejects a layout that needs bounded rounds", + "[unit][mpi][neighbor][async][context][bounded][persistent]") { +#if KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT && \ + !KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT_C + communicator_view const world{MPI_COMM_WORLD}; + distributed_graph graph{world, {world.rank()}}; + require_collective_semantic_error( + [&] { + neighbor_all_to_all_v_context context{ + graph, + {5}, + context_options{ + .collective = collective_options{.mpi3_round_ceiling = 2}, + .persistence = persistence_policy::required}}; + static_cast(context); + }, + "persistent neighborhood exchange requires a single representable " + "payload", + world); +#endif +} + +TEST_CASE("asymmetric backend masks select one common backend collectively", + "[unit][mpi][neighbor][async][backend][agreement]") { + communicator_view const world{MPI_COMM_WORLD}; + if (world.size() < 2) { + return; + } + + using parhip::mpi::detail::agree_neighbor_backend_masks; + using parhip::mpi::detail::backend_bit; + using parhip::mpi::detail::choose_direct_backend; + using parhip::mpi::detail::filter_local_backend_masks; + using parhip::mpi::detail::neighbor_backend_mask; + using parhip::mpi::detail::neighbor_backend_masks; + using parhip::mpi::detail::neighbor_direct_backend; + + auto const bit = [](neighbor_direct_backend backend) { + return backend_bit(backend); + }; + auto const immediate_legacy = bit(neighbor_direct_backend::immediate_legacy); + auto const immediate_large = + bit(neighbor_direct_backend::immediate_large_count); + auto const persistent_legacy = + bit(neighbor_direct_backend::persistent_legacy); + auto const persistent_large = + bit(neighbor_direct_backend::persistent_large_count); + auto const all_backends = + immediate_legacy | immediate_large | persistent_legacy | persistent_large; + struct agreement_case final { + std::string_view name; + persistence_policy policy; + neighbor_backend_mask even_available; + neighbor_backend_mask odd_available; + bool even_physical_legacy = true; + bool odd_physical_legacy = true; + neighbor_backend_mask expected_allowed; + neighbor_backend_mask expected_physical; + std::optional expected; + }; + auto const cases = std::array{ + agreement_case{ + .name = "disabled keeps only immediate backends", + .policy = persistence_policy::disabled, + .even_available = all_backends, + .odd_available = immediate_large | persistent_legacy, + .expected_allowed = immediate_large, + .expected_physical = immediate_large | persistent_legacy, + .expected = neighbor_direct_backend::immediate_large_count, + }, + agreement_case{ + .name = "prefer gives persistent precedence", + .policy = persistence_policy::prefer, + .even_available = all_backends, + .odd_available = persistent_legacy | immediate_legacy, + .expected_allowed = persistent_legacy | immediate_legacy, + .expected_physical = persistent_legacy | immediate_legacy, + .expected = neighbor_direct_backend::persistent_legacy, + }, + agreement_case{ + .name = "required removes immediate backends", + .policy = persistence_policy::required, + .even_available = all_backends, + .odd_available = persistent_legacy | immediate_legacy, + .expected_allowed = persistent_legacy, + .expected_physical = persistent_legacy | immediate_legacy, + .expected = neighbor_direct_backend::persistent_legacy, + }, + agreement_case{ + .name = "complementary capabilities have no common backend", + .policy = persistence_policy::prefer, + .even_available = immediate_legacy, + .odd_available = immediate_large, + .expected_allowed = 0, + .expected_physical = 0, + .expected = std::nullopt, + }, + agreement_case{ + .name = "required persistent layout failure leaves immediate " + "physically available", + .policy = persistence_policy::required, + .even_available = immediate_large | persistent_legacy, + .odd_available = immediate_large | persistent_legacy, + .even_physical_legacy = true, + .odd_physical_legacy = false, + .expected_allowed = 0, + .expected_physical = immediate_large, + .expected = std::nullopt, + }, + }; + + for (auto const& test_case : cases) { + INFO(test_case.name); + auto const even = world.rank() % 2 == 0; + auto const local = filter_local_backend_masks( + even ? test_case.even_available : test_case.odd_available, + test_case.policy, true, + even ? test_case.even_physical_legacy : test_case.odd_physical_legacy, + true); + auto common = neighbor_backend_masks{}; + { + backend_agreement_probe::activation observation{}; + common = agree_neighbor_backend_masks(local, world); + REQUIRE(backend_agreement_probe::band_calls == 1); + } + auto const selected = choose_direct_backend(common.allowed); + REQUIRE(selected == test_case.expected); + REQUIRE(common.allowed == test_case.expected_allowed); + REQUIRE(common.physical == test_case.expected_physical); + + auto const encoded = selected.has_value() + ? static_cast(*selected) + : std::numeric_limits::max(); + auto minimum = std::uint64_t{0}; + auto maximum = std::uint64_t{0}; + REQUIRE(PMPI_Allreduce(&encoded, &minimum, 1, MPI_UINT64_T, MPI_MIN, + world.native_handle()) == MPI_SUCCESS); + REQUIRE(PMPI_Allreduce(&encoded, &maximum, 1, MPI_UINT64_T, MPI_MAX, + world.native_handle()) == MPI_SUCCESS); + REQUIRE(minimum == maximum); + } +} +} // namespace diff --git a/parallel/parallel_src/tests/communication/mpi_failure_policy_probe.cpp b/parallel/parallel_src/tests/communication/mpi_failure_policy_probe.cpp new file mode 100644 index 00000000..bbb973ee --- /dev/null +++ b/parallel/parallel_src/tests/communication/mpi_failure_policy_probe.cpp @@ -0,0 +1,926 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_adapter.h" +#include "kahip_mpi_capabilities.h" + +namespace failure_probe { +struct dense_wire_record final { + std::uint64_t value; + + auto operator==(dense_wire_record const&) const -> bool = default; +}; +} // namespace failure_probe + +template <> +struct parhip::mpi::wire_members { + inline static constexpr auto value = + std::tuple{&failure_probe::dense_wire_record::value}; +}; + +static_assert(std::is_standard_layout_v); +static_assert(std::is_trivially_copyable_v); + +namespace { +enum class failure_mode { + pre_init_error, + semantic_factory_resource, + error_string_secondary, + null_communicator, + intercommunicator, + null_distributed_graph, + wrong_topology, + capacity_resolver, + dense_receive_offset_capacity, + dense_receive_byte_capacity, + distributed_graph_degree_capacity, + neighbor_receive_offset_capacity, + neighbor_receive_byte_capacity, + neighbor_bounded_round_arithmetic, +}; + +auto selected_mode = failure_mode::pre_init_error; +auto pre_initialization_control_is_active = false; +auto pre_initialization_mpi_calls = 0; +auto injection_is_armed = false; +auto track_next_duplicate = false; +auto tracked_communicator = MPI_COMM_NULL; +auto error_string_attempts = 0; +auto cleanup_attempts = 0; +auto capacity_allreduce_attempts = 0; +auto dense_count_exchange_attempts = 0; +auto dense_payload_attempts = 0; +auto dense_datatype_attempts = 0; +auto graph_semantic_validation_attempts = 0; +auto graph_create_attempts = 0; +auto neighbor_count_exchange_attempts = 0; +auto neighbor_payload_attempts = 0; +auto neighbor_datatype_attempts = 0; +auto neighbor_phase_round_attempts = 0; +auto neighbor_capacity_after_injection_attempts = 0; +auto neighbor_graph_communicator = MPI_COMM_NULL; +auto cached_rank = -1; +auto cached_size = -1; + +constexpr auto original_backend_error = 17291; +constexpr auto secondary_formatter_error = 17292; + +[[nodiscard]] auto is_dense_capacity_mode() noexcept -> bool { + return selected_mode == failure_mode::dense_receive_offset_capacity || + selected_mode == failure_mode::dense_receive_byte_capacity; +} + +[[nodiscard]] auto is_graph_capacity_mode() noexcept -> bool { + return selected_mode == failure_mode::distributed_graph_degree_capacity; +} + +[[nodiscard]] auto is_neighbor_capacity_mode() noexcept -> bool { + return selected_mode == failure_mode::neighbor_receive_offset_capacity || + selected_mode == failure_mode::neighbor_receive_byte_capacity || + selected_mode == failure_mode::neighbor_bounded_round_arithmetic; +} + +[[nodiscard]] auto is_neighbor_receive_capacity_mode() noexcept -> bool { + return selected_mode == failure_mode::neighbor_receive_offset_capacity || + selected_mode == failure_mode::neighbor_receive_byte_capacity; +} + +void record_pre_initialization_mpi_call(char const* operation) noexcept { + if (!pre_initialization_control_is_active) { + return; + } + ++pre_initialization_mpi_calls; + std::fprintf(stderr, + "forbidden MPI call while constructing pre-init mpi_error: %s\n", + operation); +} + +[[noreturn]] void forbidden_failure_path_call(char const* operation) noexcept { + std::fprintf(stderr, "forbidden MPI call or cleanup after injection: %s\n", + operation); + std::_Exit(90); +} + +void record_forbidden_datatype_attempt(char const* operation) noexcept { + if (is_neighbor_receive_capacity_mode()) { + ++neighbor_datatype_attempts; + } else { + ++dense_datatype_attempts; + } + forbidden_failure_path_call(operation); +} + +[[nodiscard]] auto affected_name(MPI_Comm communicator) noexcept + -> std::string_view { + if (communicator == tracked_communicator) { + switch (selected_mode) { + case failure_mode::semantic_factory_resource: + return "semantic"; + case failure_mode::error_string_secondary: + return "backend"; + case failure_mode::null_communicator: + case failure_mode::intercommunicator: + return "communicator-guard"; + case failure_mode::null_distributed_graph: + return "graph-guard"; + case failure_mode::wrong_topology: + return "topology"; + case failure_mode::capacity_resolver: + return "capacity"; + case failure_mode::dense_receive_offset_capacity: + case failure_mode::dense_receive_byte_capacity: + return "dense-operation"; + case failure_mode::distributed_graph_degree_capacity: + return "graph-validation"; + case failure_mode::neighbor_receive_offset_capacity: + case failure_mode::neighbor_receive_byte_capacity: + case failure_mode::neighbor_bounded_round_arithmetic: + return "neighbor-operation"; + case failure_mode::pre_init_error: + break; + } + } + if (communicator == neighbor_graph_communicator) { + return "neighbor-graph"; + } + if (communicator == MPI_COMM_WORLD) { + return "world"; + } + if (communicator == MPI_COMM_NULL) { + return "null"; + } + return "other"; +} + +[[noreturn]] void returned_from_failure(char const* mode) noexcept { + std::fprintf(stderr, "returned-from-failure: %s\n", mode); + std::_Exit(2); +} + +void write_abort_observation(std::string_view affected) noexcept { + constexpr auto buffer_capacity = std::size_t{512}; + static_assert(buffer_capacity <= PIPE_BUF); + auto buffer = std::array{}; + auto const length = std::snprintf( + buffer.data(), buffer.size(), + "observed MPI_Abort rank=%d affected=%.*s " + "error-string-attempts=%d cleanup-attempts=%d " + "capacity-allreduce-attempts=%d " + "dense-count-exchange-attempts=%d " + "dense-payload-attempts=%d dense-datatype-attempts=%d " + "graph-semantic-validation-attempts=%d " + "graph-create-attempts=%d " + "neighbor-count-exchange-attempts=%d " + "neighbor-payload-attempts=%d " + "neighbor-datatype-attempts=%d " + "neighbor-phase-round-attempts=%d " + "neighbor-capacity-after-injection-attempts=%d\n", + cached_rank, static_cast(affected.size()), affected.data(), + error_string_attempts, cleanup_attempts, capacity_allreduce_attempts, + dense_count_exchange_attempts, dense_payload_attempts, + dense_datatype_attempts, graph_semantic_validation_attempts, + graph_create_attempts, neighbor_count_exchange_attempts, + neighbor_payload_attempts, neighbor_datatype_attempts, + neighbor_phase_round_attempts, + neighbor_capacity_after_injection_attempts); + if (length < 0 || static_cast(length) >= buffer.size() || + ::write(STDERR_FILENO, buffer.data(), static_cast(length)) != + length) { + std::_Exit(89); + } +} +} // namespace + +static_assert(noexcept(write_abort_observation({}))); + +extern "C" int MPI_Error_string(int error_code, + char* error_text, + int* error_text_length) { + if (pre_initialization_control_is_active) { + record_pre_initialization_mpi_call("MPI_Error_string"); + if (error_text != nullptr) { + error_text[0] = '\0'; + } + if (error_text_length != nullptr) { + *error_text_length = 0; + } + return MPI_ERR_OTHER; + } + if (injection_is_armed) { + ++error_string_attempts; + if (selected_mode != failure_mode::error_string_secondary || + error_string_attempts != 1) { + forbidden_failure_path_call("MPI_Error_string"); + } + std::fprintf(stderr, + "injected MPI_Error_string failure original=%d secondary=%d\n", + original_backend_error, secondary_formatter_error); + return secondary_formatter_error; + } + return PMPI_Error_string(error_code, error_text, error_text_length); +} + +extern "C" int MPI_Error_class(int error_code, int* error_class) { + if (pre_initialization_control_is_active) { + record_pre_initialization_mpi_call("MPI_Error_class"); + if (error_class != nullptr) { + *error_class = error_code; + } + return MPI_SUCCESS; + } + if (injection_is_armed) { + forbidden_failure_path_call("MPI_Error_class"); + } + return PMPI_Error_class(error_code, error_class); +} + +extern "C" int MPI_Initialized(int* initialized) { + if (pre_initialization_control_is_active) { + record_pre_initialization_mpi_call("MPI_Initialized"); + if (initialized != nullptr) { + *initialized = 0; + } + return MPI_SUCCESS; + } + return PMPI_Initialized(initialized); +} + +extern "C" int MPI_Finalized(int* finalized) { + if (pre_initialization_control_is_active) { + record_pre_initialization_mpi_call("MPI_Finalized"); + if (finalized != nullptr) { + *finalized = 0; + } + return MPI_SUCCESS; + } + return PMPI_Finalized(finalized); +} + +extern "C" int MPI_Abort(MPI_Comm communicator, int error_code) { + if (pre_initialization_control_is_active) { + record_pre_initialization_mpi_call("MPI_Abort"); + return MPI_ERR_OTHER; + } + if (injection_is_armed) { + auto const affected = affected_name(communicator); + if (std::fflush(stderr) != 0) { + std::_Exit(89); + } + write_abort_observation(affected); + std::_Exit(86); + } + return PMPI_Abort(communicator, error_code); +} + +extern "C" int MPI_Allreduce(void const* send_buffer, + void* receive_buffer, + int count, + MPI_Datatype datatype, + MPI_Op operation, + MPI_Comm communicator) { + if (is_graph_capacity_mode() && communicator == tracked_communicator) { + if (count == 1 && datatype == MPI_INT && operation == MPI_MIN) { + ++graph_semantic_validation_attempts; + return PMPI_Allreduce(send_buffer, receive_buffer, count, datatype, + operation, communicator); + } + if (count != 2 || datatype != MPI_UINT64_T || operation != MPI_BOR || + send_buffer == nullptr || receive_buffer == nullptr) { + forbidden_failure_path_call( + "MPI_Allreduce(distributed graph capacity shape)"); + } + + ++capacity_allreduce_attempts; + if (capacity_allreduce_attempts != 1) { + forbidden_failure_path_call( + "MPI_Allreduce(distributed graph capacity count)"); + } + auto injected = + std::array{static_cast(send_buffer)[0], + static_cast(send_buffer)[1]}; + if (cached_rank == 0) { + injected[0] |= parhip::mpi::capacity_issue_mask( + parhip::mpi::capacity_issue::topology_degree_not_representable); + std::fputs("injected rank-zero distributed graph degree capacity\n", + stderr); + } + injection_is_armed = true; + return PMPI_Allreduce(injected.data(), receive_buffer, count, datatype, + operation, communicator); + } + if (is_neighbor_capacity_mode() && communicator == tracked_communicator) { + if (selected_mode == failure_mode::neighbor_bounded_round_arithmetic && + count == 1 && datatype == MPI_UINT64_T && operation == MPI_MAX) { + ++neighbor_phase_round_attempts; + if (neighbor_phase_round_attempts > cached_size) { + forbidden_failure_path_call( + "MPI_Allreduce(neighbor bounded phase count)"); + } + auto injected = *static_cast(send_buffer); + if (neighbor_phase_round_attempts == 1 && cached_rank == 0) { + injected = std::numeric_limits::max(); + std::fputs( + "injected rank-zero bounded neighbor round arithmetic capacity\n", + stderr); + } + if (neighbor_phase_round_attempts == 1) { + injection_is_armed = true; + } + return PMPI_Allreduce(&injected, receive_buffer, count, datatype, + operation, communicator); + } + if (count == 2 && datatype == MPI_UINT64_T && operation == MPI_BOR) { + ++capacity_allreduce_attempts; + if (injection_is_armed) { + ++neighbor_capacity_after_injection_attempts; + } + auto const expected_capacity_attempts = + selected_mode == failure_mode::neighbor_bounded_round_arithmetic ? 2 + : 1; + if (capacity_allreduce_attempts > expected_capacity_attempts || + neighbor_capacity_after_injection_attempts > 1 || + send_buffer == nullptr || receive_buffer == nullptr) { + forbidden_failure_path_call( + "MPI_Allreduce(neighbor capacity resolver shape)"); + } + return PMPI_Allreduce(send_buffer, receive_buffer, count, datatype, + operation, communicator); + } + if (injection_is_armed) { + forbidden_failure_path_call("MPI_Allreduce(neighbor failure path)"); + } + return PMPI_Allreduce(send_buffer, receive_buffer, count, datatype, + operation, communicator); + } + if (injection_is_armed) { + if (selected_mode != failure_mode::capacity_resolver && + !is_dense_capacity_mode()) { + forbidden_failure_path_call("MPI_Allreduce"); + } + ++capacity_allreduce_attempts; + if (capacity_allreduce_attempts != 1 || send_buffer == nullptr || + receive_buffer == nullptr || count != 2 || datatype != MPI_UINT64_T || + operation != MPI_BOR || communicator != tracked_communicator) { + forbidden_failure_path_call("MPI_Allreduce(capacity resolver shape)"); + } + } + return PMPI_Allreduce(send_buffer, receive_buffer, count, datatype, operation, + communicator); +} + +extern "C" int MPI_Dist_graph_create(MPI_Comm old_communicator, + int source_count, + int const sources[], + int const degrees[], + int const destinations[], + int const weights[], + MPI_Info info, + int reorder, + MPI_Comm* graph_communicator) { + if (is_graph_capacity_mode()) { + ++graph_create_attempts; + if (injection_is_armed) { + forbidden_failure_path_call("MPI_Dist_graph_create"); + } + } + return PMPI_Dist_graph_create(old_communicator, source_count, sources, + degrees, destinations, weights, info, reorder, + graph_communicator); +} + +extern "C" int MPI_Alltoall(void const* send_buffer, + int send_count, + MPI_Datatype send_datatype, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + auto const result = + PMPI_Alltoall(send_buffer, send_count, send_datatype, receive_buffer, + receive_count, receive_datatype, communicator); + if (!is_dense_capacity_mode()) { + return result; + } + + ++dense_count_exchange_attempts; + if (result != MPI_SUCCESS || dense_count_exchange_attempts != 1 || + cached_size != 2 || receive_buffer == nullptr || send_count != 1 || + receive_count != 1 || send_datatype != MPI_UINT64_T || + receive_datatype != MPI_UINT64_T || + communicator != tracked_communicator) { + forbidden_failure_path_call("MPI_Alltoall(dense count exchange shape)"); + } + + if (cached_rank == 0) { + auto* counts = static_cast(receive_buffer); + static_assert(sizeof(std::size_t) <= sizeof(std::uint64_t)); + if (selected_mode == failure_mode::dense_receive_offset_capacity) { + counts[0] = std::numeric_limits::max(); + counts[1] = std::uint64_t{1}; + std::fputs("injected rank-zero dense receive offset capacity\n", stderr); + } else { + counts[0] = std::numeric_limits::max() / + sizeof(failure_probe::dense_wire_record) + + std::uint64_t{1}; + counts[1] = std::uint64_t{0}; + std::fputs("injected rank-zero dense receive byte capacity\n", stderr); + } + } + injection_is_armed = true; + return result; +} + +extern "C" int MPI_Neighbor_alltoall(void const* send_buffer, + int send_count, + MPI_Datatype send_datatype, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + auto const result = PMPI_Neighbor_alltoall( + send_buffer, send_count, send_datatype, receive_buffer, receive_count, + receive_datatype, communicator); + if (!is_neighbor_capacity_mode()) { + return result; + } + + ++neighbor_count_exchange_attempts; + if (result != MPI_SUCCESS || neighbor_count_exchange_attempts != 1 || + cached_size != 2 || receive_buffer == nullptr || send_count != 1 || + receive_count != 1 || send_datatype != MPI_UINT64_T || + receive_datatype != MPI_UINT64_T || + communicator != tracked_communicator) { + forbidden_failure_path_call( + "MPI_Neighbor_alltoall(neighbor count exchange shape)"); + } + + if (is_neighbor_receive_capacity_mode()) { + if (cached_rank == 0) { + auto* counts = static_cast(receive_buffer); + static_assert(sizeof(std::size_t) <= sizeof(std::uint64_t)); + if (selected_mode == failure_mode::neighbor_receive_offset_capacity) { + counts[0] = std::numeric_limits::max(); + counts[1] = std::uint64_t{1}; + std::fputs("injected rank-zero neighbor receive offset capacity\n", + stderr); + } else { + counts[0] = std::numeric_limits::max() / + sizeof(failure_probe::dense_wire_record) + + std::uint64_t{1}; + counts[1] = std::uint64_t{0}; + std::fputs("injected rank-zero neighbor receive byte capacity\n", + stderr); + } + } + injection_is_armed = true; + } + return result; +} + +extern "C" int MPI_Alltoallv(void const* send_buffer, + int const* send_counts, + int const* send_displacements, + MPI_Datatype send_datatype, + void* receive_buffer, + int const* receive_counts, + int const* receive_displacements, + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (injection_is_armed && is_dense_capacity_mode()) { + ++dense_payload_attempts; + forbidden_failure_path_call("MPI_Alltoallv(dense payload)"); + } + return PMPI_Alltoallv(send_buffer, send_counts, send_displacements, + send_datatype, receive_buffer, receive_counts, + receive_displacements, receive_datatype, communicator); +} + +#if KAHIP_HAVE_MPI_ALLTOALLV_C +extern "C" int MPI_Alltoallv_c(void const* send_buffer, + MPI_Count const* send_counts, + MPI_Aint const* send_displacements, + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const* receive_counts, + MPI_Aint const* receive_displacements, + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (injection_is_armed && is_dense_capacity_mode()) { + ++dense_payload_attempts; + forbidden_failure_path_call("MPI_Alltoallv_c(dense payload)"); + } + return PMPI_Alltoallv_c(send_buffer, send_counts, send_displacements, + send_datatype, receive_buffer, receive_counts, + receive_displacements, receive_datatype, + communicator); +} +#endif + +extern "C" int MPI_Neighbor_alltoallv(void const* send_buffer, + int const* send_counts, + int const* send_displacements, + MPI_Datatype send_datatype, + void* receive_buffer, + int const* receive_counts, + int const* receive_displacements, + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (injection_is_armed && is_neighbor_capacity_mode()) { + ++neighbor_payload_attempts; + forbidden_failure_path_call("MPI_Neighbor_alltoallv(neighbor payload)"); + } + return PMPI_Neighbor_alltoallv(send_buffer, send_counts, send_displacements, + send_datatype, receive_buffer, receive_counts, + receive_displacements, receive_datatype, + communicator); +} + +#if KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C +extern "C" int MPI_Neighbor_alltoallv_c(void const* send_buffer, + MPI_Count const* send_counts, + MPI_Aint const* send_displacements, + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const* receive_counts, + MPI_Aint const* receive_displacements, + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (injection_is_armed && is_neighbor_capacity_mode()) { + ++neighbor_payload_attempts; + forbidden_failure_path_call("MPI_Neighbor_alltoallv_c(neighbor payload)"); + } + return PMPI_Neighbor_alltoallv_c(send_buffer, send_counts, send_displacements, + send_datatype, receive_buffer, + receive_counts, receive_displacements, + receive_datatype, communicator); +} +#endif + +extern "C" int MPI_Get_address(void const* location, MPI_Aint* address) { + if (injection_is_armed && + (is_dense_capacity_mode() || is_neighbor_receive_capacity_mode())) { + record_forbidden_datatype_attempt("MPI_Get_address"); + } + return PMPI_Get_address(location, address); +} + +extern "C" int MPI_Type_create_struct(int count, + int const block_lengths[], + MPI_Aint const displacements[], + MPI_Datatype const datatypes[], + MPI_Datatype* new_datatype) { + if (injection_is_armed && + (is_dense_capacity_mode() || is_neighbor_receive_capacity_mode())) { + record_forbidden_datatype_attempt("MPI_Type_create_struct"); + } + return PMPI_Type_create_struct(count, block_lengths, displacements, datatypes, + new_datatype); +} + +extern "C" int MPI_Type_create_resized(MPI_Datatype old_datatype, + MPI_Aint lower_bound, + MPI_Aint extent, + MPI_Datatype* new_datatype) { + if (injection_is_armed && + (is_dense_capacity_mode() || is_neighbor_receive_capacity_mode())) { + record_forbidden_datatype_attempt("MPI_Type_create_resized"); + } + return PMPI_Type_create_resized(old_datatype, lower_bound, extent, + new_datatype); +} + +extern "C" int MPI_Type_commit(MPI_Datatype* datatype) { + if (injection_is_armed && + (is_dense_capacity_mode() || is_neighbor_receive_capacity_mode())) { + record_forbidden_datatype_attempt("MPI_Type_commit"); + } + return PMPI_Type_commit(datatype); +} + +extern "C" int MPI_Type_free(MPI_Datatype* datatype) { + if (injection_is_armed && + (is_dense_capacity_mode() || is_neighbor_receive_capacity_mode())) { + record_forbidden_datatype_attempt("MPI_Type_free"); + } + return PMPI_Type_free(datatype); +} + +extern "C" int MPI_Comm_dup(MPI_Comm communicator, + MPI_Comm* duplicate_communicator) { + auto const result = PMPI_Comm_dup(communicator, duplicate_communicator); + if (result == MPI_SUCCESS && track_next_duplicate && + duplicate_communicator != nullptr) { + tracked_communicator = *duplicate_communicator; + track_next_duplicate = false; + if (selected_mode == failure_mode::wrong_topology) { + injection_is_armed = true; + std::fputs("captured wrong-topology internal duplicate\n", stderr); + } else if (is_dense_capacity_mode()) { + std::fputs("captured dense operation duplicate\n", stderr); + } else if (is_graph_capacity_mode()) { + std::fputs("captured distributed-graph validation duplicate\n", stderr); + } else if (is_neighbor_capacity_mode()) { + std::fputs("captured neighbor operation duplicate\n", stderr); + } + } + return result; +} + +extern "C" int MPI_Comm_free(MPI_Comm* communicator) { + if (injection_is_armed && communicator != nullptr && + *communicator == tracked_communicator) { + ++cleanup_attempts; + forbidden_failure_path_call("MPI_Comm_free(tracked duplicate)"); + } + return PMPI_Comm_free(communicator); +} + +namespace { +auto run_pre_initialization_control() -> int { + pre_initialization_control_is_active = true; + pre_initialization_mpi_calls = 0; + + constexpr auto context = std::string_view{"pre-init structured error"}; + auto const failure = + parhip::mpi::mpi_error{MPI_ERR_ARG, std::string{context}}; + auto const message = std::string_view{failure.what()}; + auto const structured = failure.error_code() == MPI_ERR_ARG && + failure.context() == context && + failure.location().line() > 0 && + message.find(context) != std::string_view::npos; + pre_initialization_control_is_active = false; + + if (!structured || pre_initialization_mpi_calls != 0) { + std::fprintf( + stderr, + "pre-init mpi_error control failed: structured=%d mpi-calls=%d\n", + structured ? 1 : 0, pre_initialization_mpi_calls); + return 2; + } + + std::fprintf(stderr, + "pre-init mpi_error remained MPI-free: raw=%d mpi-calls=0\n", + failure.error_code()); + return 0; +} + +[[noreturn]] void run_semantic_factory_resource_failure() { + auto affected = + parhip::mpi::communicator{parhip::mpi::communicator_view{MPI_COMM_WORLD}}; + tracked_communicator = affected.native_handle(); + injection_is_armed = true; + + parhip::mpi::detail::throw_collectively_agreed_semantic_error_from( + affected.native_handle(), []() -> parhip::mpi::mpi_error { + std::fputs("injected semantic factory bad_alloc\n", stderr); + throw std::bad_alloc{}; + }); + returned_from_failure("semantic-factory-resource"); +} + +[[noreturn]] void run_error_string_secondary_failure() { + auto affected = + parhip::mpi::communicator{parhip::mpi::communicator_view{MPI_COMM_WORLD}}; + tracked_communicator = affected.native_handle(); + injection_is_armed = true; + + parhip::mpi::abort_on_mpi_error(affected.native_handle(), + original_backend_error, + "backend formatter failure"); +} + +[[noreturn]] void run_wrong_topology_failure() { + track_next_duplicate = true; + try { + auto invalid = + parhip::mpi::topology{parhip::mpi::communicator_view{MPI_COMM_WORLD}}; + static_cast(invalid); + } catch (...) { + returned_from_failure("wrong-topology was catchable"); + } + returned_from_failure("wrong-topology"); +} + +[[noreturn]] void run_null_communicator_failure() { + std::fputs("injected null communicator construction\n", stderr); + injection_is_armed = true; + auto invalid = + parhip::mpi::communicator{parhip::mpi::communicator_view{MPI_COMM_NULL}}; + static_cast(invalid); + returned_from_failure("null-communicator"); +} + +[[noreturn]] void run_intercommunicator_failure() { + MPI_Comm local = MPI_COMM_NULL; + if (PMPI_Comm_split(MPI_COMM_WORLD, cached_rank, 0, &local) != MPI_SUCCESS) { + returned_from_failure("intercommunicator local split"); + } + + MPI_Comm intercommunicator = MPI_COMM_NULL; + auto const remote_leader = cached_rank == 0 ? 1 : 0; + if (PMPI_Intercomm_create(local, 0, MPI_COMM_WORLD, remote_leader, 71, + &intercommunicator) != MPI_SUCCESS) { + returned_from_failure("intercommunicator creation"); + } + if (PMPI_Comm_free(&local) != MPI_SUCCESS) { + returned_from_failure("intercommunicator local cleanup"); + } + + tracked_communicator = intercommunicator; + std::fputs("injected intercommunicator construction\n", stderr); + injection_is_armed = true; + auto invalid = parhip::mpi::communicator{ + parhip::mpi::communicator_view{intercommunicator}}; + static_cast(invalid); + returned_from_failure("intercommunicator"); +} + +[[noreturn]] void run_null_distributed_graph_failure() { + std::fputs("injected null distributed graph construction\n", stderr); + injection_is_armed = true; + auto invalid = parhip::mpi::distributed_graph{ + parhip::mpi::communicator_view{MPI_COMM_NULL}, {}}; + static_cast(invalid); + returned_from_failure("null-distributed-graph"); +} + +[[noreturn]] void run_capacity_resolver_failure() { + auto affected = + parhip::mpi::communicator{parhip::mpi::communicator_view{MPI_COMM_WORLD}}; + tracked_communicator = affected.native_handle(); + injection_is_armed = true; + + auto local = parhip::mpi::capacity_result{}; + if (cached_rank == 0) { + local = parhip::mpi::with_fatal_capacity_issue( + local, parhip::mpi::capacity_issue::cumulative_offset_overflow); + std::fputs("injected rank-zero fatal capacity issue\n", stderr); + } else { + local = parhip::mpi::with_bounded_capacity_issue( + local, + parhip::mpi::capacity_issue::collective_layout_not_representable); + } + static_cast(parhip::mpi::resolve_capacity_collectively( + local, affected.native_handle(), affected.native_handle(), + "capacity resolver probe")); + returned_from_failure("capacity-resolver"); +} + +[[noreturn]] void run_dense_receive_capacity_failure(char const* mode) { + auto segments = std::vector>( + static_cast(cached_size)); + for (auto& segment : segments) { + segment.push_back(failure_probe::dense_wire_record{ + .value = static_cast(cached_rank + 1)}); + } + auto sends = parhip::mpi::segmented_buffer< + failure_probe::dense_wire_record>::from_segments(segments); + track_next_duplicate = true; + static_cast(parhip::mpi::all_to_all_v( + std::move(sends), parhip::mpi::communicator_view{MPI_COMM_WORLD})); + returned_from_failure(mode); +} + +[[noreturn]] void run_distributed_graph_degree_capacity_failure() { + track_next_duplicate = true; + auto graph = parhip::mpi::distributed_graph{ + parhip::mpi::communicator_view{MPI_COMM_WORLD}, {cached_rank}}; + static_cast(graph); + returned_from_failure("distributed-graph-degree-capacity"); +} + +[[noreturn]] void run_neighbor_capacity_failure(char const* mode) { + auto graph = parhip::mpi::distributed_graph{ + parhip::mpi::communicator_view{MPI_COMM_WORLD}, {0, 1}}; + neighbor_graph_communicator = graph.native_handle(); + + auto const values_per_destination = + selected_mode == failure_mode::neighbor_bounded_round_arithmetic + ? std::size_t{3} + : std::size_t{1}; + auto segments = std::vector>( + graph.destinations().size()); + for (auto& segment : segments) { + segment.resize(values_per_destination, + failure_probe::dense_wire_record{ + .value = static_cast(cached_rank + 1)}); + } + + track_next_duplicate = true; + auto const options = + selected_mode == failure_mode::neighbor_bounded_round_arithmetic + ? parhip::mpi::collective_options{ + .mpi3_round_ceiling = 2, + .force_mpi3 = true, + } + : parhip::mpi::collective_options{}; + static_cast(parhip::mpi::neighbor_all_to_all_v( + parhip::mpi::segmented_buffer< + failure_probe::dense_wire_record>::from_segments(segments), + graph, options)); + returned_from_failure(mode); +} +} // namespace + +int main(int argc, char* argv[]) { + if (argc != 2) { + std::fputs("usage: mpi_failure_policy_probe MODE\n", stderr); + return 64; + } + auto const mode = std::string_view{argv[1]}; + if (mode == "pre-init-error") { + selected_mode = failure_mode::pre_init_error; + return run_pre_initialization_control(); + } + if (mode == "semantic-factory-resource") { + selected_mode = failure_mode::semantic_factory_resource; + } else if (mode == "error-string-secondary") { + selected_mode = failure_mode::error_string_secondary; + } else if (mode == "null-communicator") { + selected_mode = failure_mode::null_communicator; + } else if (mode == "intercommunicator") { + selected_mode = failure_mode::intercommunicator; + } else if (mode == "null-distributed-graph") { + selected_mode = failure_mode::null_distributed_graph; + } else if (mode == "wrong-topology") { + selected_mode = failure_mode::wrong_topology; + } else if (mode == "capacity-resolver") { + selected_mode = failure_mode::capacity_resolver; + } else if (mode == "dense-receive-offset-capacity") { + selected_mode = failure_mode::dense_receive_offset_capacity; + } else if (mode == "dense-receive-byte-capacity") { + selected_mode = failure_mode::dense_receive_byte_capacity; + } else if (mode == "distributed-graph-degree-capacity") { + selected_mode = failure_mode::distributed_graph_degree_capacity; + } else if (mode == "neighbor-receive-offset-capacity") { + selected_mode = failure_mode::neighbor_receive_offset_capacity; + } else if (mode == "neighbor-receive-byte-capacity") { + selected_mode = failure_mode::neighbor_receive_byte_capacity; + } else if (mode == "neighbor-bounded-round-arithmetic") { + selected_mode = failure_mode::neighbor_bounded_round_arithmetic; + } else { + std::fprintf(stderr, "unknown failure-policy mode: %s\n", argv[1]); + return 64; + } + + auto const initialization_result = MPI_Init(&argc, &argv); + if (initialization_result != MPI_SUCCESS) { + std::fprintf(stderr, "MPI_Init returned raw error %d\n", + initialization_result); + return 70; + } + if (PMPI_Comm_rank(MPI_COMM_WORLD, &cached_rank) != MPI_SUCCESS) { + std::fputs("PMPI_Comm_rank failed before failure injection\n", stderr); + return 70; + } + if (PMPI_Comm_size(MPI_COMM_WORLD, &cached_size) != MPI_SUCCESS) { + std::fputs("PMPI_Comm_size failed before failure injection\n", stderr); + return 70; + } + + switch (selected_mode) { + case failure_mode::semantic_factory_resource: + run_semantic_factory_resource_failure(); + case failure_mode::error_string_secondary: + run_error_string_secondary_failure(); + case failure_mode::null_communicator: + run_null_communicator_failure(); + case failure_mode::intercommunicator: + run_intercommunicator_failure(); + case failure_mode::null_distributed_graph: + run_null_distributed_graph_failure(); + case failure_mode::wrong_topology: + run_wrong_topology_failure(); + case failure_mode::capacity_resolver: + run_capacity_resolver_failure(); + case failure_mode::dense_receive_offset_capacity: + run_dense_receive_capacity_failure("dense-receive-offset-capacity"); + case failure_mode::dense_receive_byte_capacity: + run_dense_receive_capacity_failure("dense-receive-byte-capacity"); + case failure_mode::distributed_graph_degree_capacity: + run_distributed_graph_degree_capacity_failure(); + case failure_mode::neighbor_receive_offset_capacity: + run_neighbor_capacity_failure("neighbor-receive-offset-capacity"); + case failure_mode::neighbor_receive_byte_capacity: + run_neighbor_capacity_failure("neighbor-receive-byte-capacity"); + case failure_mode::neighbor_bounded_round_arithmetic: + run_neighbor_capacity_failure("neighbor-bounded-round-arithmetic"); + case failure_mode::pre_init_error: + break; + } + returned_from_failure("unknown-active-mode"); +} diff --git a/parallel/parallel_src/tests/communication/mpi_fixed_broadcast_failure_probe.cpp b/parallel/parallel_src/tests/communication/mpi_fixed_broadcast_failure_probe.cpp new file mode 100644 index 00000000..c0f71408 --- /dev/null +++ b/parallel/parallel_src/tests/communication/mpi_fixed_broadcast_failure_probe.cpp @@ -0,0 +1,395 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_fixed_broadcast.h" +#include "definitions.h" +#include "io/parallel_graph_io.h" + +// KAHIP_PMPI_CALLBACK_REGION_BEGIN +namespace fixed_broadcast_failure_probe { +enum class mode : unsigned char { + status, + header, + partition_map, + mismatched_map, + previous_cut, + previous_weight, + missing_file, + truncated_header, + invalid_version, + intercommunicator, +}; + +inline bool active = false; +inline mode selected = mode::status; +inline MPI_Comm expected_communicator = MPI_COMM_NULL; +inline int broadcasts = 0; +inline int finalizations = 0; +inline bool callback_error = false; + +void write_text(std::string_view text) noexcept { + static_cast(::write(STDERR_FILENO, text.data(), text.size())); +} + +[[nodiscard]] auto valid_broadcast(int index, + MPI_Count count, + MPI_Datatype datatype, + int root, + MPI_Comm communicator, + void const* buffer) noexcept -> bool { + if (communicator != expected_communicator || buffer == nullptr || root != 0) { + return false; + } + switch (selected) { + case mode::status: + return index == 1 && count == 1 && datatype == MPI_INT; + case mode::header: + return index == 1 && count == 3 && datatype == MPI_UNSIGNED_LONG_LONG; + case mode::partition_map: + return index == 1 && count == 5 && datatype == MPI_INT; + case mode::mismatched_map: + return false; + case mode::previous_cut: + if (index == 1) { + return count == 5 && datatype == MPI_INT; + } + return index == 2 && count == 1 && datatype == MPI_UNSIGNED_LONG_LONG; + case mode::previous_weight: + if (index == 1) { + return count == 5 && datatype == MPI_INT; + } + return (index == 2 || index == 3) && count == 1 && + datatype == MPI_UNSIGNED_LONG_LONG; + case mode::missing_file: + case mode::truncated_header: + return index == 1 && count == 1 && datatype == MPI_INT; + case mode::invalid_version: + if (index == 1) { + return count == 1 && datatype == MPI_INT; + } + return index == 2 && count == 3 && datatype == MPI_UNSIGNED_LONG_LONG; + case mode::intercommunicator: + return false; + } + return false; +} + +[[nodiscard]] auto should_fail_broadcast(int index) noexcept -> bool { + switch (selected) { + case mode::status: + case mode::header: + case mode::partition_map: + return index == 1; + case mode::mismatched_map: + return false; + case mode::previous_cut: + return index == 2; + case mode::previous_weight: + return index == 3; + case mode::missing_file: + case mode::truncated_header: + case mode::invalid_version: + return false; + case mode::intercommunicator: + return false; + } + return true; +} + +[[nodiscard]] auto expected_abort_state() noexcept -> bool { + if (callback_error || finalizations != 0) { + return false; + } + switch (selected) { + case mode::status: + case mode::header: + case mode::partition_map: + case mode::missing_file: + case mode::truncated_header: + return broadcasts == 1; + case mode::mismatched_map: + return broadcasts == 0; + case mode::previous_cut: + case mode::invalid_version: + return broadcasts == 2; + case mode::previous_weight: + return broadcasts == 3; + case mode::intercommunicator: + return broadcasts == 0; + } + return false; +} + +[[noreturn]] void observed_abort(MPI_Comm communicator, + int error_code) noexcept { + int relation = MPI_UNEQUAL; + if (error_code != EXIT_FAILURE || + PMPI_Comm_compare(communicator, expected_communicator, &relation) != + MPI_SUCCESS || + relation != MPI_IDENT || !expected_abort_state()) { + write_text("observed fixed-broadcast MPI_Abort with unexpected state\n"); + std::_Exit(91); + } + write_text( + "observed fixed-broadcast MPI_Abort on affected communicator; " + "internal MPI_Finalize counter is zero\n"); + if (PMPI_Barrier(MPI_COMM_WORLD) != MPI_SUCCESS) { + std::_Exit(90); + } + std::_Exit(86); +} +} // namespace fixed_broadcast_failure_probe + +static_assert(noexcept(fixed_broadcast_failure_probe::write_text({}))); +static_assert( + noexcept(fixed_broadcast_failure_probe::valid_broadcast(0, + 0, + MPI_DATATYPE_NULL, + 0, + MPI_COMM_NULL, + nullptr))); +static_assert( + noexcept(fixed_broadcast_failure_probe::should_fail_broadcast(0))); +static_assert(noexcept(fixed_broadcast_failure_probe::expected_abort_state())); +static_assert( + noexcept(fixed_broadcast_failure_probe::observed_abort(MPI_COMM_NULL, 0))); + +extern "C" int MPI_Bcast(void* buffer, + int count, + MPI_Datatype datatype, + int root, + MPI_Comm communicator) { + if (!fixed_broadcast_failure_probe::active) { + return PMPI_Bcast(buffer, count, datatype, root, communicator); + } + auto const index = ++fixed_broadcast_failure_probe::broadcasts; + if (!fixed_broadcast_failure_probe::valid_broadcast( + index, count, datatype, root, communicator, buffer)) { + fixed_broadcast_failure_probe::callback_error = true; + return MPI_ERR_OTHER; + } + if (fixed_broadcast_failure_probe::should_fail_broadcast(index)) { + return MPI_ERR_OTHER; + } + return PMPI_Bcast(buffer, count, datatype, root, communicator); +} + +#if KAHIP_HAVE_MPI_BCAST_C +extern "C" int MPI_Bcast_c(void* buffer, + MPI_Count count, + MPI_Datatype datatype, + int root, + MPI_Comm communicator) { + if (!fixed_broadcast_failure_probe::active) { + return PMPI_Bcast_c(buffer, count, datatype, root, communicator); + } + auto const index = ++fixed_broadcast_failure_probe::broadcasts; + if (!fixed_broadcast_failure_probe::valid_broadcast( + index, count, datatype, root, communicator, buffer)) { + fixed_broadcast_failure_probe::callback_error = true; + return MPI_ERR_OTHER; + } + if (fixed_broadcast_failure_probe::should_fail_broadcast(index)) { + return MPI_ERR_OTHER; + } + return PMPI_Bcast_c(buffer, count, datatype, root, communicator); +} +#endif + +extern "C" int MPI_Finalize() { + if (fixed_broadcast_failure_probe::active) { + ++fixed_broadcast_failure_probe::finalizations; + return MPI_SUCCESS; + } + return PMPI_Finalize(); +} + +extern "C" int MPI_Abort(MPI_Comm communicator, int error_code) { + fixed_broadcast_failure_probe::observed_abort(communicator, error_code); +} +// KAHIP_PMPI_CALLBACK_REGION_END + +namespace { +[[nodiscard]] auto parse_mode(std::string_view value) + -> fixed_broadcast_failure_probe::mode { + using mode = fixed_broadcast_failure_probe::mode; + if (value == "status") { + return mode::status; + } + if (value == "header") { + return mode::header; + } + if (value == "partition-map") { + return mode::partition_map; + } + if (value == "mismatched-map") { + return mode::mismatched_map; + } + if (value == "previous-cut") { + return mode::previous_cut; + } + if (value == "previous-weight") { + return mode::previous_weight; + } + if (value == "missing-file") { + return mode::missing_file; + } + if (value == "truncated-header") { + return mode::truncated_header; + } + if (value == "invalid-version") { + return mode::invalid_version; + } + if (value == "intercommunicator") { + return mode::intercommunicator; + } + fixed_broadcast_failure_probe::write_text("unknown failure-probe mode\n"); + std::_Exit(2); +} + +void write_graph_failure_file(std::string_view path, + int communicator_rank, + MPI_Comm communicator, + fixed_broadcast_failure_probe::mode selected) { + if (communicator_rank == 0) { + auto output = + std::ofstream{path.data(), std::ios::binary | std::ios::trunc}; + if (selected == fixed_broadcast_failure_probe::mode::invalid_version) { + auto const header = std::array{4, 0, 0}; + output.write(reinterpret_cast(header.data()), + static_cast(sizeof(header))); + } else { + constexpr auto truncated_header = std::byte{0x04}; + output.write(reinterpret_cast(&truncated_header), 1); + } + } + if (PMPI_Barrier(communicator) != MPI_SUCCESS) { + std::_Exit(3); + } +} + +[[nodiscard]] auto make_intercommunicator(int world_rank) -> MPI_Comm { + auto local = MPI_COMM_NULL; + if (PMPI_Comm_split(MPI_COMM_WORLD, world_rank, 0, &local) != MPI_SUCCESS || + local == MPI_COMM_NULL) { + std::_Exit(7); + } + auto intercommunicator = MPI_COMM_NULL; + if (PMPI_Intercomm_create(local, 0, MPI_COMM_WORLD, 1 - world_rank, 719, + &intercommunicator) != MPI_SUCCESS || + intercommunicator == MPI_COMM_NULL) { + std::_Exit(8); + } + if (PMPI_Comm_free(&local) != MPI_SUCCESS) { + std::_Exit(9); + } + return intercommunicator; +} +} // namespace + +int main(int argc, char** argv) { + if (argc != 3 || MPI_Init(&argc, &argv) != MPI_SUCCESS) { + return 2; + } + + auto world_rank = 0; + auto world_size = 0; + if (MPI_Comm_rank(MPI_COMM_WORLD, &world_rank) != MPI_SUCCESS || + MPI_Comm_size(MPI_COMM_WORLD, &world_size) != MPI_SUCCESS) { + return 3; + } + + using mode = fixed_broadcast_failure_probe::mode; + auto const selected = parse_mode(argv[1]); + auto communicator = MPI_COMM_NULL; + if (selected == mode::intercommunicator) { + if (world_size != 2) { + return 4; + } + communicator = make_intercommunicator(world_rank); + } else if (MPI_Comm_split(MPI_COMM_WORLD, 0, world_size - world_rank, + &communicator) != MPI_SUCCESS || + communicator == MPI_COMM_NULL) { + return 4; + } + if (MPI_Comm_set_errhandler(communicator, MPI_ERRORS_RETURN) != MPI_SUCCESS) { + return 5; + } + + auto rank = -1; + auto size = 0; + if (MPI_Comm_rank(communicator, &rank) != MPI_SUCCESS || + MPI_Comm_size(communicator, &size) != MPI_SUCCESS) { + return 6; + } + + if (selected == mode::truncated_header || selected == mode::invalid_version) { + write_graph_failure_file(argv[2], rank, communicator, selected); + } + + fixed_broadcast_failure_probe::selected = selected; + fixed_broadcast_failure_probe::expected_communicator = communicator; + fixed_broadcast_failure_probe::active = true; + auto const view = parhip::mpi::communicator_view{communicator}; + + switch (selected) { + case mode::status: { + auto value = 1; + parhip::mpi::broadcast_fixed(value, 0, view, + "MPI_Bcast(binary graph read status)"); + break; + } + case mode::header: { + auto value = std::array{3, 11, 17}; + parhip::mpi::broadcast_fixed(std::span{value}, 0, view, + "MPI_Bcast(binary graph header)"); + break; + } + case mode::partition_map: + case mode::previous_cut: + case mode::previous_weight: { + auto partition_map = std::array{2, 3, 5, 7, 11}; + auto previous_cut = parhip::EdgeWeight{23}; + auto previous_weight = parhip::NodeWeight{29}; + parhip::mpi::broadcast_vcycle_state( + std::span{partition_map}, previous_cut, previous_weight, 0, view); + break; + } + case mode::mismatched_map: { + auto partition_map = std::array{2, 3, 5, 7, 11}; + auto previous_cut = parhip::EdgeWeight{23}; + auto previous_weight = parhip::NodeWeight{29}; + auto const map_size = rank == 0 ? std::size_t{5} : std::size_t{4}; + parhip::mpi::broadcast_vcycle_state( + std::span{partition_map.data(), map_size}, previous_cut, + previous_weight, 0, view); + break; + } + case mode::missing_file: + case mode::truncated_header: + case mode::invalid_version: { + auto config = parhip::PPartitionConfig{}; + auto graph = parhip::parallel_graph_access{communicator}; + static_cast(parhip::parallel_graph_io::readGraphBinary( + config, graph, argv[2], rank, size, communicator)); + break; + } + case mode::intercommunicator: { + auto values = std::array{2, 3, 5}; + parhip::mpi::broadcast_bounded(std::span{values}, 0, view, + "MPI_Bcast(intercommunicator probe)"); + break; + } + } + + fixed_broadcast_failure_probe::write_text( + "fixed-broadcast operation returned without fail-fast\n"); + std::_Exit(92); +} diff --git a/parallel/parallel_src/tests/communication/mpi_fixed_broadcast_mpi_test.cpp b/parallel/parallel_src/tests/communication/mpi_fixed_broadcast_mpi_test.cpp new file mode 100644 index 00000000..3a7e7416 --- /dev/null +++ b/parallel/parallel_src/tests/communication/mpi_fixed_broadcast_mpi_test.cpp @@ -0,0 +1,317 @@ +#include + +#include + +#include +#include +#include +#include + +#include "communication/mpi_fixed_broadcast.h" +#include "definitions.h" + +// KAHIP_PMPI_CALLBACK_REGION_BEGIN +namespace fixed_broadcast_probe { +enum class backend : unsigned char { legacy, large_count }; + +struct operation final { + backend selected_backend = backend::legacy; + MPI_Count count = 0; + MPI_Datatype datatype = MPI_DATATYPE_NULL; + int root = -1; + MPI_Comm communicator = MPI_COMM_NULL; +}; + +struct counters final { + std::array operations{}; + int operation_count = 0; + bool overflow = false; +}; + +inline bool active = false; +inline MPI_Comm expected_communicator = MPI_COMM_NULL; +inline counters observed{}; + +void reset(MPI_Comm communicator) noexcept { + expected_communicator = communicator; + observed = {}; +} + +void record(backend selected_backend, + MPI_Count count, + MPI_Datatype datatype, + int root, + MPI_Comm communicator) noexcept { + if (!active) { + return; + } + if (observed.operation_count >= + static_cast(observed.operations.size())) { + observed.overflow = true; + return; + } + observed.operations[static_cast(observed.operation_count++)] = + operation{.selected_backend = selected_backend, + .count = count, + .datatype = datatype, + .root = root, + .communicator = communicator}; +} + +class activation final { + public: + explicit activation(MPI_Comm communicator) noexcept { + reset(communicator); + active = true; + } + + ~activation() noexcept { active = false; } + + activation(activation const&) = delete; + auto operator=(activation const&) -> activation& = delete; +}; +} // namespace fixed_broadcast_probe + +static_assert(noexcept(fixed_broadcast_probe::reset(MPI_COMM_NULL))); +static_assert(noexcept( + fixed_broadcast_probe::record(fixed_broadcast_probe::backend::legacy, + 0, + MPI_DATATYPE_NULL, + 0, + MPI_COMM_NULL))); + +extern "C" int MPI_Bcast(void* buffer, + int count, + MPI_Datatype datatype, + int root, + MPI_Comm communicator) { + fixed_broadcast_probe::record(fixed_broadcast_probe::backend::legacy, count, + datatype, root, communicator); + return PMPI_Bcast(buffer, count, datatype, root, communicator); +} + +#if KAHIP_HAVE_MPI_BCAST_C +extern "C" int MPI_Bcast_c(void* buffer, + MPI_Count count, + MPI_Datatype datatype, + int root, + MPI_Comm communicator) { + fixed_broadcast_probe::record(fixed_broadcast_probe::backend::large_count, + count, datatype, root, communicator); + return PMPI_Bcast_c(buffer, count, datatype, root, communicator); +} +#endif +// KAHIP_PMPI_CALLBACK_REGION_END + +namespace { +constexpr auto root = 0; + +[[nodiscard]] constexpr auto large_value(unsigned long long offset) noexcept + -> parhip::ULONG { + return parhip::ULONG{std::numeric_limits::max()} + offset; +} + +void require_operation(fixed_broadcast_probe::operation const& operation, + fixed_broadcast_probe::backend expected_backend, + MPI_Count expected_count, + MPI_Datatype expected_datatype, + MPI_Comm expected_communicator) { + REQUIRE(operation.selected_backend == expected_backend); + REQUIRE(operation.count == expected_count); + REQUIRE(operation.datatype == expected_datatype); + REQUIRE(operation.root == root); + REQUIRE(operation.communicator == expected_communicator); +} +} // namespace + +TEST_CASE("fixed graph-header broadcasts preserve native value domains") { + auto world_rank = 0; + auto world_size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &world_rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &world_size) == MPI_SUCCESS); + REQUIRE(world_size >= 1); + REQUIRE(world_size <= 5); + + auto communicator = MPI_COMM_NULL; + REQUIRE(MPI_Comm_split(MPI_COMM_WORLD, 0, world_size - world_rank, + &communicator) == MPI_SUCCESS); + REQUIRE(communicator != MPI_COMM_NULL); + + auto rank = -1; + REQUIRE(MPI_Comm_rank(communicator, &rank) == MPI_SUCCESS); + + auto status = rank == root ? 1 : -1; + auto header = rank == root ? std::array{parhip::ULONG{3}, + large_value(17), + large_value(41)} + : std::array{}; + fixed_broadcast_probe::counters observed{}; + { + fixed_broadcast_probe::activation const probe{communicator}; + parhip::mpi::broadcast_fixed(status, root, + parhip::mpi::communicator_view{communicator}, + "MPI_Bcast(binary graph read status)"); + parhip::mpi::broadcast_fixed(std::span{header}, root, + parhip::mpi::communicator_view{communicator}, + "MPI_Bcast(binary graph header)"); + observed = fixed_broadcast_probe::observed; + } + + REQUIRE(status == 1); + REQUIRE(header == std::array{ + parhip::ULONG{3}, large_value(17), large_value(41)}); + REQUIRE_FALSE(observed.overflow); + REQUIRE(observed.operation_count == 2); + require_operation(observed.operations[0], + fixed_broadcast_probe::backend::legacy, 1, MPI_INT, + communicator); + require_operation(observed.operations[1], + fixed_broadcast_probe::backend::legacy, 3, + MPI_UNSIGNED_LONG_LONG, communicator); + + REQUIRE(MPI_Comm_free(&communicator) == MPI_SUCCESS); +} + +TEST_CASE("vcycle state broadcast uses map then cut then weight") { + auto world_rank = 0; + auto world_size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &world_rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &world_size) == MPI_SUCCESS); + + auto communicator = MPI_COMM_NULL; + REQUIRE(MPI_Comm_split(MPI_COMM_WORLD, 0, world_size - world_rank, + &communicator) == MPI_SUCCESS); + auto rank = -1; + REQUIRE(MPI_Comm_rank(communicator, &rank) == MPI_SUCCESS); + + auto partition_map = + rank == root ? std::array{3, 1, 4, 1, 5} : std::array{}; + auto previous_cut = rank == root ? large_value(73) : parhip::EdgeWeight{}; + auto previous_maximum_block_weight = + rank == root ? large_value(101) : parhip::NodeWeight{}; + + fixed_broadcast_probe::counters observed{}; + { + fixed_broadcast_probe::activation const probe{communicator}; + parhip::mpi::broadcast_vcycle_state( + std::span{partition_map}, previous_cut, previous_maximum_block_weight, + root, parhip::mpi::communicator_view{communicator}); + observed = fixed_broadcast_probe::observed; + } + + REQUIRE(partition_map == std::array{3, 1, 4, 1, 5}); + REQUIRE(previous_cut == large_value(73)); + REQUIRE(previous_maximum_block_weight == large_value(101)); + REQUIRE_FALSE(observed.overflow); + REQUIRE(observed.operation_count == 3); +#if KAHIP_HAVE_MPI_BCAST_C + constexpr auto map_backend = fixed_broadcast_probe::backend::large_count; +#else + constexpr auto map_backend = fixed_broadcast_probe::backend::legacy; +#endif + require_operation(observed.operations[0], map_backend, 5, MPI_INT, + communicator); + require_operation(observed.operations[1], + fixed_broadcast_probe::backend::legacy, 1, + MPI_UNSIGNED_LONG_LONG, communicator); + require_operation(observed.operations[2], + fixed_broadcast_probe::backend::legacy, 1, + MPI_UNSIGNED_LONG_LONG, communicator); + + REQUIRE(MPI_Comm_free(&communicator) == MPI_SUCCESS); +} + +TEST_CASE("vcycle map uses deterministic bounded MPI-3 rounds") { + auto world_rank = 0; + auto world_size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &world_rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &world_size) == MPI_SUCCESS); + + auto communicator = MPI_COMM_NULL; + REQUIRE(MPI_Comm_split(MPI_COMM_WORLD, 0, world_size - world_rank, + &communicator) == MPI_SUCCESS); + auto rank = -1; + REQUIRE(MPI_Comm_rank(communicator, &rank) == MPI_SUCCESS); + + auto partition_map = + rank == root ? std::array{2, 7, 1, 8, 2} : std::array{}; + auto previous_cut = rank == root ? large_value(113) : parhip::EdgeWeight{}; + auto previous_maximum_block_weight = + rank == root ? large_value(127) : parhip::NodeWeight{}; + + fixed_broadcast_probe::counters observed{}; + { + fixed_broadcast_probe::activation const probe{communicator}; + parhip::mpi::broadcast_vcycle_state( + std::span{partition_map}, previous_cut, previous_maximum_block_weight, + root, parhip::mpi::communicator_view{communicator}, + {.mpi3_round_ceiling = 2, .force_mpi3 = true}); + observed = fixed_broadcast_probe::observed; + } + + REQUIRE(partition_map == std::array{2, 7, 1, 8, 2}); + REQUIRE(previous_cut == large_value(113)); + REQUIRE(previous_maximum_block_weight == large_value(127)); + REQUIRE_FALSE(observed.overflow); + REQUIRE(observed.operation_count == 5); + require_operation(observed.operations[0], + fixed_broadcast_probe::backend::legacy, 2, MPI_INT, + communicator); + require_operation(observed.operations[1], + fixed_broadcast_probe::backend::legacy, 2, MPI_INT, + communicator); + require_operation(observed.operations[2], + fixed_broadcast_probe::backend::legacy, 1, MPI_INT, + communicator); + require_operation(observed.operations[3], + fixed_broadcast_probe::backend::legacy, 1, + MPI_UNSIGNED_LONG_LONG, communicator); + require_operation(observed.operations[4], + fixed_broadcast_probe::backend::legacy, 1, + MPI_UNSIGNED_LONG_LONG, communicator); + + REQUIRE(MPI_Comm_free(&communicator) == MPI_SUCCESS); +} + +TEST_CASE("vcycle state retains a zero-length map collective") { + auto world_rank = 0; + auto world_size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &world_rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &world_size) == MPI_SUCCESS); + + auto communicator = MPI_COMM_NULL; + REQUIRE(MPI_Comm_split(MPI_COMM_WORLD, 0, world_size - world_rank, + &communicator) == MPI_SUCCESS); + auto rank = -1; + REQUIRE(MPI_Comm_rank(communicator, &rank) == MPI_SUCCESS); + + auto previous_cut = rank == root ? large_value(131) : parhip::EdgeWeight{}; + auto previous_maximum_block_weight = + rank == root ? large_value(137) : parhip::NodeWeight{}; + + fixed_broadcast_probe::counters observed{}; + { + fixed_broadcast_probe::activation const probe{communicator}; + parhip::mpi::broadcast_vcycle_state( + std::span{}, previous_cut, previous_maximum_block_weight, root, + parhip::mpi::communicator_view{communicator}, + {.mpi3_round_ceiling = 2, .force_mpi3 = true}); + observed = fixed_broadcast_probe::observed; + } + + REQUIRE(previous_cut == large_value(131)); + REQUIRE(previous_maximum_block_weight == large_value(137)); + REQUIRE_FALSE(observed.overflow); + REQUIRE(observed.operation_count == 3); + require_operation(observed.operations[0], + fixed_broadcast_probe::backend::legacy, 0, MPI_INT, + communicator); + require_operation(observed.operations[1], + fixed_broadcast_probe::backend::legacy, 1, + MPI_UNSIGNED_LONG_LONG, communicator); + require_operation(observed.operations[2], + fixed_broadcast_probe::backend::legacy, 1, + MPI_UNSIGNED_LONG_LONG, communicator); + + REQUIRE(MPI_Comm_free(&communicator) == MPI_SUCCESS); +} diff --git a/parallel/parallel_src/tests/communication/mpi_lifecycle_failure_probe.cpp b/parallel/parallel_src/tests/communication/mpi_lifecycle_failure_probe.cpp new file mode 100644 index 00000000..dc6eaa28 --- /dev/null +++ b/parallel/parallel_src/tests/communication/mpi_lifecycle_failure_probe.cpp @@ -0,0 +1,134 @@ +#include + +#include +#include +#include +#include +#include + +#include "communication/mpi_failure.h" + +namespace { +enum class failure_mode { + initialized_query, + finalized_query, + passthrough, +}; + +auto selected_mode = failure_mode::initialized_query; +constexpr auto injected_query_error = 17293; +auto initialized_calls = 0; +auto finalized_calls = 0; + +[[noreturn]] void forbidden_mpi_call(char const* name) noexcept { + std::fprintf(stderr, "forbidden MPI call after lifecycle-query failure: %s\n", + name); + std::_Exit(90); +} + +[[noreturn]] void observed_abort(int) noexcept { + constexpr char message[] = + "observed SIGABRT from lifecycle-query failure\n"; + static_assert(sizeof(message) - 1 < 512); + // One short write is below POSIX's minimum atomic pipe-write size. If it + // nevertheless fails or is incomplete, the verifier fails closed because + // the complete marker is absent. + static_cast(::write(STDERR_FILENO, message, sizeof(message) - 1)); + std::_Exit(86); +} +} // namespace + +extern "C" int MPI_Initialized(int* flag) { + ++initialized_calls; + if (selected_mode == failure_mode::initialized_query) { + if (initialized_calls != 1) { + forbidden_mpi_call("MPI_Initialized retry"); + } + return injected_query_error; + } + if (selected_mode == failure_mode::finalized_query) { + if (initialized_calls != 1) { + forbidden_mpi_call("MPI_Initialized retry"); + } + *flag = 1; + return MPI_SUCCESS; + } + return PMPI_Initialized(flag); +} + +extern "C" int MPI_Finalized(int* flag) { + ++finalized_calls; + if (selected_mode == failure_mode::finalized_query) { + if (initialized_calls != 1 || finalized_calls != 1) { + forbidden_mpi_call("MPI_Finalized retry or out-of-sequence query"); + } + return injected_query_error; + } + if (selected_mode == failure_mode::initialized_query) { + forbidden_mpi_call("MPI_Finalized after MPI_Initialized failure"); + } + return PMPI_Finalized(flag); +} + +extern "C" int MPI_Error_string(int, char*, int*) { + forbidden_mpi_call("MPI_Error_string"); +} + +extern "C" int MPI_Abort(MPI_Comm, int) { + forbidden_mpi_call("MPI_Abort"); +} + +int main(int argc, char* argv[]) { + if (argc != 2) { + std::fprintf(stderr, "usage: mpi_lifecycle_failure_probe MODE\n"); + return 64; + } + auto const mode = std::string_view{argv[1]}; + if (mode == "initialized") { + selected_mode = failure_mode::initialized_query; + } else if (mode == "finalized") { + selected_mode = failure_mode::finalized_query; + } else if (mode == "before-initialization") { + selected_mode = failure_mode::passthrough; + if (parhip::mpi::runtime_is_active()) { + std::fputs("MPI unexpectedly active before initialization\n", stderr); + return 2; + } + return 0; + } else if (mode == "post-finalization") { + selected_mode = failure_mode::passthrough; + auto const init_result = MPI_Init(&argc, &argv); + if (init_result != MPI_SUCCESS) { + std::fprintf(stderr, "MPI_Init returned raw error %d\n", init_result); + return 70; + } + if (!parhip::mpi::runtime_is_active()) { + std::fputs("MPI unexpectedly inactive after initialization\n", stderr); + return 2; + } + auto const finalize_result = MPI_Finalize(); + if (finalize_result != MPI_SUCCESS) { + std::fprintf( + stderr, "MPI_Finalize returned raw error %d\n", finalize_result); + return 70; + } + if (parhip::mpi::runtime_is_active()) { + std::fputs("MPI unexpectedly active after finalization\n", stderr); + return 2; + } + return 0; + } else { + std::fprintf(stderr, "unknown lifecycle failure mode: %s\n", argv[1]); + return 64; + } + + if (std::signal(SIGABRT, observed_abort) == SIG_ERR) { + std::fputs("could not install SIGABRT observation handler\n", stderr); + return 70; + } + auto const active = parhip::mpi::runtime_is_active(); + std::fprintf(stderr, + "runtime_is_active returned unexpectedly: %s\n", + active ? "active" : "inactive"); + return active ? 2 : 0; +} diff --git a/parallel/parallel_src/tests/communication/mpi_neighborhood_failure_probe.cpp b/parallel/parallel_src/tests/communication/mpi_neighborhood_failure_probe.cpp new file mode 100644 index 00000000..ab1d9f74 --- /dev/null +++ b/parallel/parallel_src/tests/communication/mpi_neighborhood_failure_probe.cpp @@ -0,0 +1,130 @@ +#include + +#include +#include +#include +#include + +#include "communication/mpi_adapter.h" + +namespace { +enum class failure_mode { + create, + query, + free, +}; + +auto selected_mode = failure_mode::create; +auto graph_communicator = MPI_COMM_NULL; + +[[nodiscard]] auto mode_name() noexcept -> std::string_view { + switch (selected_mode) { + case failure_mode::create: + return "create"; + case failure_mode::query: + return "query"; + case failure_mode::free: + return "free"; + } + return "unknown"; +} + +[[nodiscard]] auto affected_name(MPI_Comm communicator) noexcept + -> std::string_view { + if (communicator == MPI_COMM_NULL) { + return "null"; + } + if (communicator == graph_communicator) { + return "graph"; + } + if (communicator == MPI_COMM_WORLD) { + return "world"; + } + return "internal"; +} +} // namespace + +extern "C" int MPI_Dist_graph_create(MPI_Comm old_communicator, + int source_count, + int const sources[], + int const degrees[], + int const destinations[], + int const weights[], + MPI_Info info, + int reorder, + MPI_Comm* result_communicator) { + if (selected_mode == failure_mode::create) { + return MPI_ERR_OTHER; + } + auto const result = PMPI_Dist_graph_create( + old_communicator, source_count, sources, degrees, destinations, weights, + info, reorder, result_communicator); + if (result == MPI_SUCCESS) { + graph_communicator = *result_communicator; + } + return result; +} + +extern "C" int MPI_Dist_graph_neighbors_count(MPI_Comm communicator, + int* indegree, + int* outdegree, + int* weighted) { + if (selected_mode == failure_mode::query && + communicator == graph_communicator) { + return MPI_ERR_OTHER; + } + return PMPI_Dist_graph_neighbors_count(communicator, indegree, outdegree, + weighted); +} + +extern "C" int MPI_Comm_free(MPI_Comm* communicator) { + if (selected_mode == failure_mode::free && + *communicator == graph_communicator) { + return MPI_ERR_OTHER; + } + return PMPI_Comm_free(communicator); +} + +extern "C" int MPI_Abort(MPI_Comm communicator, int) { + auto const mode = mode_name(); + auto const affected = affected_name(communicator); + std::fprintf(stderr, + "observed MPI_Abort from neighborhood-%.*s failure on %.*s " + "communicator\n", + static_cast(mode.size()), mode.data(), + static_cast(affected.size()), affected.data()); + std::_Exit(86); +} + +int main(int argc, char* argv[]) { + if (argc != 2) { + std::fputs("usage: mpi_neighborhood_failure_probe MODE\n", stderr); + return 64; + } + auto const mode = std::string_view{argv[1]}; + if (mode == "create") { + selected_mode = failure_mode::create; + } else if (mode == "query") { + selected_mode = failure_mode::query; + } else if (mode == "free") { + selected_mode = failure_mode::free; + } else { + std::fprintf(stderr, "unknown neighborhood failure mode: %s\n", argv[1]); + return 64; + } + + auto const init_result = MPI_Init(&argc, &argv); + if (init_result != MPI_SUCCESS) { + std::fprintf(stderr, "MPI_Init returned raw error %d\n", init_result); + return 70; + } + { + parhip::mpi::communicator_view const world{MPI_COMM_WORLD}; + parhip::mpi::distributed_graph graph{world, {world.rank()}}; + } + + std::fprintf(stderr, "neighborhood-%.*s failure did not abort\n", + static_cast(mode.size()), mode.data()); + static_cast(MPI_Finalize()); + return 2; +} diff --git a/parallel/parallel_src/tests/communication/mpi_tools_failure_probe.cpp b/parallel/parallel_src/tests/communication/mpi_tools_failure_probe.cpp new file mode 100644 index 00000000..cab36fb5 --- /dev/null +++ b/parallel/parallel_src/tests/communication/mpi_tools_failure_probe.cpp @@ -0,0 +1,300 @@ +#include +#include + +#include +#include +#include +#include +#include + +#include "communication/mpi_tools.h" +#include "communication/serial_kernel_profile_observer.h" +#include "data_structure/parallel_graph_access.h" + +// KAHIP_PMPI_CALLBACK_REGION_BEGIN +namespace mpi_tools_failure_probe { +inline bool active = false; +inline MPI_Comm expected_communicator = MPI_COMM_NULL; +inline int communicator_rank = -1; +inline int all_to_all_calls = 0; +inline int finalizations = 0; +inline bool callback_error = false; +inline bool unsafe_profile = false; +inline bool structural_self_loop = false; +inline bool vcycle_mismatch = false; +inline bool initial_algorithm_mismatch = false; +inline bool profile_aggregate_overflow = false; + +void write_text(std::string_view text) noexcept { + static_cast(::write(STDERR_FILENO, text.data(), text.size())); +} + +void observe_safe_serial_profile( + void*, kahip::serial_kernel::serial_kernel_profile const&) noexcept { + callback_error = true; + write_text("misleading safe observer profile\n"); +} + +[[nodiscard]] auto valid_count_exchange(void const* send_buffer, + int send_count, + MPI_Datatype send_datatype, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_datatype, + MPI_Comm communicator) noexcept + -> bool { + int relation = MPI_UNEQUAL; + return send_buffer != nullptr && receive_buffer != nullptr && + send_count == 1 && receive_count == 1 && + send_datatype == MPI_UINT64_T && receive_datatype == MPI_UINT64_T && + PMPI_Comm_compare(communicator, expected_communicator, &relation) == + MPI_SUCCESS && + (relation == MPI_IDENT || relation == MPI_CONGRUENT); +} + +[[nodiscard]] auto expected_abort_state() noexcept -> bool { + return !callback_error && + all_to_all_calls == + ((unsafe_profile || structural_self_loop || vcycle_mismatch || + initial_algorithm_mismatch) + || profile_aggregate_overflow + ? 0 + : 1) && + finalizations == 0 && + (communicator_rank == 0 || communicator_rank == 1); +} + +[[noreturn]] void observed_abort(MPI_Comm communicator, + int error_code) noexcept { + int relation = MPI_UNEQUAL; + if (error_code != EXIT_FAILURE || + PMPI_Comm_compare(communicator, expected_communicator, &relation) != + MPI_SUCCESS || + (relation != MPI_IDENT && relation != MPI_CONGRUENT) || + !expected_abort_state()) { + write_text("observed mpi-tools MPI_Abort with unexpected state\n"); + std::_Exit(91); + } + if (profile_aggregate_overflow && communicator_rank == 0) { + write_text("observed MPI_Abort rank=0 " + "mpi-tools-profile-aggregate-overflow " + "affected-communicator; payload all-to-all calls are zero\n"); + } else if (profile_aggregate_overflow) { + write_text("observed MPI_Abort rank=1 " + "mpi-tools-profile-aggregate-overflow " + "affected-communicator; payload all-to-all calls are zero\n"); + } else if (vcycle_mismatch && communicator_rank == 0) { + write_text("observed MPI_Abort rank=0 mpi-tools-config-vcycle-mismatch " + "affected-communicator; payload all-to-all calls are zero\n"); + } else if (vcycle_mismatch) { + write_text("observed MPI_Abort rank=1 mpi-tools-config-vcycle-mismatch " + "affected-communicator; payload all-to-all calls are zero\n"); + } else if (initial_algorithm_mismatch && communicator_rank == 0) { + write_text("observed MPI_Abort rank=0 " + "mpi-tools-config-initial-algorithm-mismatch " + "affected-communicator; payload all-to-all calls are zero\n"); + } else if (initial_algorithm_mismatch) { + write_text("observed MPI_Abort rank=1 " + "mpi-tools-config-initial-algorithm-mismatch " + "affected-communicator; payload all-to-all calls are zero\n"); + } else if (structural_self_loop && communicator_rank == 0) { + write_text("observed MPI_Abort rank=0 mpi-tools-structural-self-loop " + "affected-communicator\n"); + } else if (structural_self_loop) { + write_text("observed MPI_Abort rank=1 mpi-tools-structural-self-loop " + "affected-communicator\n"); + } else if (unsafe_profile && communicator_rank == 0) { + write_text("observed MPI_Abort rank=0 mpi-tools-unsafe-profile " + "affected-communicator; payload all-to-all calls are zero\n"); + } else if (unsafe_profile) { + write_text("observed MPI_Abort rank=1 mpi-tools-unsafe-profile " + "affected-communicator; payload all-to-all calls are zero\n"); + } else if (communicator_rank == 0) { + write_text( + "observed MPI_Abort rank=0 mpi-tools-count-exchange " + "affected-communicator; internal MPI_Finalize counter is zero\n"); + } else { + write_text( + "observed MPI_Abort rank=1 mpi-tools-count-exchange " + "affected-communicator; internal MPI_Finalize counter is zero\n"); + } + if (PMPI_Barrier(MPI_COMM_WORLD) != MPI_SUCCESS) { + std::_Exit(90); + } + std::_Exit(86); +} +} // namespace mpi_tools_failure_probe + +static_assert(noexcept(mpi_tools_failure_probe::write_text({}))); +static_assert( + noexcept(mpi_tools_failure_probe::valid_count_exchange(nullptr, + 0, + MPI_DATATYPE_NULL, + nullptr, + 0, + MPI_DATATYPE_NULL, + MPI_COMM_NULL))); +static_assert(noexcept(mpi_tools_failure_probe::expected_abort_state())); +static_assert(noexcept(mpi_tools_failure_probe::observed_abort(MPI_COMM_NULL, + 0))); + +extern "C" int MPI_Alltoall(void const* send_buffer, + int send_count, + MPI_Datatype send_datatype, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (!mpi_tools_failure_probe::active) { + return PMPI_Alltoall(send_buffer, send_count, send_datatype, receive_buffer, + receive_count, receive_datatype, communicator); + } + ++mpi_tools_failure_probe::all_to_all_calls; + if (mpi_tools_failure_probe::unsafe_profile) { + mpi_tools_failure_probe::write_text("forbidden payload MPI_Alltoall\n"); + mpi_tools_failure_probe::callback_error = true; + } + if (!mpi_tools_failure_probe::valid_count_exchange( + send_buffer, send_count, send_datatype, receive_buffer, receive_count, + receive_datatype, communicator)) { + mpi_tools_failure_probe::callback_error = true; + } + return MPI_ERR_OTHER; +} + +extern "C" int MPI_Finalize() { + if (mpi_tools_failure_probe::active) { + ++mpi_tools_failure_probe::finalizations; + return MPI_SUCCESS; + } + return PMPI_Finalize(); +} + +extern "C" int MPI_Abort(MPI_Comm communicator, int error_code) { + mpi_tools_failure_probe::observed_abort(communicator, error_code); +} +// KAHIP_PMPI_CALLBACK_REGION_END + +int main(int argc, char** argv) { + if (argc != 2 || + (std::string_view{argv[1]} != "backend" && + std::string_view{argv[1]} != "unsafe-profile" && + std::string_view{argv[1]} != "structural-self-loop" && + std::string_view{argv[1]} != "config-vcycle-mismatch" && + std::string_view{argv[1]} != "config-initial-algorithm-mismatch" && + std::string_view{argv[1]} != "profile-aggregate-overflow") || + MPI_Init(&argc, &argv) != MPI_SUCCESS) { + return 2; + } + + auto world_rank = 0; + auto world_size = 0; + if (MPI_Comm_rank(MPI_COMM_WORLD, &world_rank) != MPI_SUCCESS || + MPI_Comm_size(MPI_COMM_WORLD, &world_size) != MPI_SUCCESS || + world_size != 2) { + return 3; + } + + auto communicator = MPI_COMM_NULL; + if (MPI_Comm_split(MPI_COMM_WORLD, 0, world_size - world_rank, + &communicator) != MPI_SUCCESS || + communicator == MPI_COMM_NULL) { + return 4; + } + auto rank = 0; + if (MPI_Comm_rank(communicator, &rank) != MPI_SUCCESS) { + return 5; + } + + mpi_tools_failure_probe::structural_self_loop = + std::string_view{argv[1]} == "structural-self-loop"; + mpi_tools_failure_probe::vcycle_mismatch = + std::string_view{argv[1]} == "config-vcycle-mismatch"; + mpi_tools_failure_probe::initial_algorithm_mismatch = + std::string_view{argv[1]} == "config-initial-algorithm-mismatch"; + mpi_tools_failure_probe::profile_aggregate_overflow = + std::string_view{argv[1]} == "profile-aggregate-overflow"; + parhip::parallel_graph_access distributed{communicator}; + auto const local_nodes = mpi_tools_failure_probe::structural_self_loop + ? (rank == 0 ? 1 : 0) + : 1; + auto const local_edges = + mpi_tools_failure_probe::structural_self_loop ? (rank == 0 ? 1 : 0) : 0; + auto const global_nodes = + mpi_tools_failure_probe::structural_self_loop ? 1 : 2; + auto const global_edges = + mpi_tools_failure_probe::structural_self_loop ? 1 : 0; + distributed.start_construction(local_nodes, local_edges, global_nodes, + global_edges, false); + auto ranges = mpi_tools_failure_probe::structural_self_loop + ? std::vector{0, 1, 1} + : std::vector{0, 1, 2}; + auto const first = ranges[static_cast(rank)]; + auto const end = ranges[static_cast(rank + 1)]; + distributed.set_range(first, first == end ? first : end - 1); + distributed.set_range_array(ranges); + if (local_nodes != 0) { + auto const node = distributed.new_node(); + distributed.setNodeWeight( + node, mpi_tools_failure_probe::profile_aggregate_overflow + ? std::numeric_limits::max() + : 1); + distributed.setSecondPartitionIndex(node, 0); + if (local_edges != 0) { + auto const edge = distributed.new_edge(node, node); + distributed.setEdgeWeight(edge, 1); + } + } + distributed.finish_construction(); + + parhip::complete_graph_access complete{communicator}; + parhip::PPartitionConfig config{}; + mpi_tools_failure_probe::unsafe_profile = + std::string_view{argv[1]} == "unsafe-profile"; + if (mpi_tools_failure_probe::unsafe_profile) { + config.k = 0; + } + if (mpi_tools_failure_probe::structural_self_loop) { + config.k = 1; + config.upper_bound_partition = 1; + } + if (mpi_tools_failure_probe::vcycle_mismatch || + mpi_tools_failure_probe::initial_algorithm_mismatch || + mpi_tools_failure_probe::profile_aggregate_overflow) { + config.k = 1; + config.upper_bound_partition = 1; + } + if (mpi_tools_failure_probe::vcycle_mismatch) { + config.vcycle = rank == 0; + } + if (mpi_tools_failure_probe::initial_algorithm_mismatch && rank == 1) { + config.initial_partitioning_algorithm = + parhip::InitialPartitioningAlgorithm::KAFFPAEFAST; + } + mpi_tools_failure_probe::expected_communicator = communicator; + mpi_tools_failure_probe::communicator_rank = rank; + mpi_tools_failure_probe::active = !mpi_tools_failure_probe::structural_self_loop; + auto observer = + std::optional{}; + if (mpi_tools_failure_probe::unsafe_profile) { + observer.emplace(mpi_tools_failure_probe::observe_safe_serial_profile, + nullptr); + } + if (mpi_tools_failure_probe::unsafe_profile) { + parhip::mpi_tools{}.collect_parallel_graph_to_checked_serial_graph( + communicator, config, distributed, complete); + } else if (mpi_tools_failure_probe::structural_self_loop) { + parhip::mpi_tools{}.collect_parallel_graph_to_checked_serial_graph( + communicator, config, distributed, complete); + } else if (mpi_tools_failure_probe::vcycle_mismatch || + mpi_tools_failure_probe::initial_algorithm_mismatch || + mpi_tools_failure_probe::profile_aggregate_overflow) { + parhip::mpi_tools{}.collect_parallel_graph_to_checked_serial_graph( + communicator, config, distributed, complete); + } else { + parhip::mpi_tools{}.collect_parallel_graph_to_local_graph( + communicator, config, distributed, complete); + } + mpi_tools_failure_probe::write_text("returned-from-failure\n"); + std::_Exit(92); +} diff --git a/parallel/parallel_src/tests/communication/mpi_trace_failure_probe.cpp b/parallel/parallel_src/tests/communication/mpi_trace_failure_probe.cpp new file mode 100644 index 00000000..9afb55c6 --- /dev/null +++ b/parallel/parallel_src/tests/communication/mpi_trace_failure_probe.cpp @@ -0,0 +1,136 @@ +#include +#include + +#include +#include +#include +#include + +#include "communication/mpi_trace.h" + +namespace { +enum class failure_mode { + rank, + allreduce, +}; + +auto mode = failure_mode::rank; +auto active = false; +auto injected_calls = 0; +auto finalize_calls = 0; +auto cached_rank = -1; + +constexpr auto injected_error = 17411; + +void write_text(std::string_view value) noexcept { + static_cast(::write(STDERR_FILENO, value.data(), value.size())); +} + +[[noreturn]] void unexpected_abort() noexcept { + write_text("observed trace MPI_Abort with unexpected state\n"); + std::_Exit(91); +} +} // namespace + +extern "C" int MPI_Comm_rank(MPI_Comm communicator, int* rank) { + if (active && mode == failure_mode::rank && injected_calls == 0) { + ++injected_calls; + write_text(cached_rank == 0 + ? "injected trace MPI_Comm_rank failure rank=0\n" + : "injected trace MPI_Comm_rank failure rank=1\n"); + return injected_error; + } + return PMPI_Comm_rank(communicator, rank); +} + +extern "C" int MPI_Allreduce(void const* send_buffer, + void* receive_buffer, + int count, + MPI_Datatype datatype, + MPI_Op operation, + MPI_Comm communicator) { + if (active && mode == failure_mode::allreduce && injected_calls == 0) { + ++injected_calls; + write_text(cached_rank == 0 + ? "injected trace MPI_Allreduce failure rank=0\n" + : "injected trace MPI_Allreduce failure rank=1\n"); + return injected_error; + } + return PMPI_Allreduce(send_buffer, receive_buffer, count, datatype, + operation, communicator); +} + +extern "C" int MPI_Finalize() { + if (active) { + ++finalize_calls; + } + return PMPI_Finalize(); +} + +extern "C" int MPI_Error_string(int error_code, + char* error_text, + int* error_text_length) { + if (active && error_code == injected_error) { + constexpr char injected_text[] = "injected trace backend failure"; + static_assert(sizeof(injected_text) <= MPI_MAX_ERROR_STRING); + if (error_text == nullptr || error_text_length == nullptr) { + return MPI_ERR_ARG; + } + for (auto index = std::size_t{0}; index < sizeof(injected_text); ++index) { + error_text[index] = injected_text[index]; + } + *error_text_length = static_cast(sizeof(injected_text) - 1); + return MPI_SUCCESS; + } + return PMPI_Error_string(error_code, error_text, error_text_length); +} + +extern "C" int MPI_Abort(MPI_Comm communicator, int error_code) { + int relation = MPI_UNEQUAL; + if (PMPI_Comm_compare(communicator, MPI_COMM_WORLD, &relation) != + MPI_SUCCESS || + (relation != MPI_IDENT && relation != MPI_CONGRUENT) || + error_code != EXIT_FAILURE || injected_calls != 1 || + finalize_calls != 0 || (cached_rank != 0 && cached_rank != 1)) { + unexpected_abort(); + } + if (cached_rank == 0) { + write_text(mode == failure_mode::rank + ? "observed MPI_Abort rank=0 trace-rank affected-communicator\n" + : "observed MPI_Abort rank=0 trace-allreduce affected-communicator\n"); + } else { + write_text(mode == failure_mode::rank + ? "observed MPI_Abort rank=1 trace-rank affected-communicator\n" + : "observed MPI_Abort rank=1 trace-allreduce affected-communicator\n"); + } + if (PMPI_Barrier(MPI_COMM_WORLD) != MPI_SUCCESS) { + std::_Exit(90); + } + std::_Exit(86); +} + +int main(int argument_count, char** argument_values) { + if (argument_count != 2 || MPI_Init(&argument_count, &argument_values) != + MPI_SUCCESS) { + return 2; + } + auto size = 0; + if (PMPI_Comm_rank(MPI_COMM_WORLD, &cached_rank) != MPI_SUCCESS || + PMPI_Comm_size(MPI_COMM_WORLD, &size) != MPI_SUCCESS || size != 2) { + return 3; + } + auto const requested_mode = std::string_view{argument_values[1]}; + if (requested_mode == "rank") { + mode = failure_mode::rank; + } else if (requested_mode == "allreduce") { + mode = failure_mode::allreduce; + } else { + return 4; + } + + active = true; + static_cast(parhip::mpi::trace::resolve_run_id_collectively( + MPI_COMM_WORLD, std::string{"common-trace-run"})); + write_text("returned-from-failure\n"); + std::_Exit(92); +} diff --git a/parallel/parallel_src/tests/communication/mpi_trace_oracle_verifier_test.cmake b/parallel/parallel_src/tests/communication/mpi_trace_oracle_verifier_test.cmake new file mode 100644 index 00000000..32cc081c --- /dev/null +++ b/parallel/parallel_src/tests/communication/mpi_trace_oracle_verifier_test.cmake @@ -0,0 +1,207 @@ +cmake_minimum_required(VERSION 4.0) + +foreach(required IN ITEMS VERIFIER WORK_DIRECTORY) + if(NOT DEFINED ${required}) + message(FATAL_ERROR "${required} is required") + endif() +endforeach() + +set(fixture_root "${WORK_DIRECTORY}/fixture") +set(trace_base "${fixture_root}/trace") +set(run_id "smoke") +set(upstream_revision "5935f349f65f1788a9b68fcf6d853e698d86956d") +set(trace_header "kahip-mpi-trace-v3 upstream=${upstream_revision}\n") +set(rank_zero_records [=[graph-distribution-node cycle=0 level=0 epoch=input iteration=0 round=0 global=7 owner=0 requester=- receiver=0 key=owner:0 weight=3 +contraction-label cycle=0 level=2 epoch=contraction iteration=0 round=0 global=7 owner=0 requester=- receiver=0 key=label:19 coarse=4 +quotient-edge cycle=0 level=2 epoch=contraction iteration=0 round=0 global=4 owner=0 requester=- receiver=0 key=target:2 weight=5 +projection-reply cycle=0 level=2 epoch=projection iteration=0 round=0 global=8 owner=1 requester=0 receiver=0 key=request:3 requester=0 owner=1 label=41 +final-partition cycle=0 level=0 epoch=final-partition iteration=0 round=0 global=9 owner=0 requester=- receiver=0 key=partition block=1 +]=]) +set(rank_one_records [=[graph-distribution-edge cycle=0 level=0 epoch=input iteration=0 round=0 global=7 owner=1 requester=- receiver=1 key=target:8 weight=4 +quotient-node-weight cycle=0 level=2 epoch=contraction iteration=0 round=0 global=4 owner=1 requester=- receiver=1 key=node weight=6 +projection-request cycle=0 level=2 epoch=projection iteration=0 round=0 global=8 owner=1 requester=0 receiver=1 key=request:3 requester=0 owner=1 +ghost-update cycle=0 level=2 epoch=projection iteration=0 round=0 global=8 owner=1 requester=- receiver=0 key=label label=41 +]=]) + +file(REMOVE_RECURSE "${WORK_DIRECTORY}") +file(MAKE_DIRECTORY "${fixture_root}") +file(WRITE "${fixture_root}/fixture.graph" "2 1\n2\n1\n") +file(WRITE "${fixture_root}/partition.txtp" "0\n1\n") +file(WRITE "${fixture_root}/oracle.patch" "trace-only oracle patch\n") +file(WRITE + "${trace_base}.run-${run_id}-fixture.rank0.trace" + "${trace_header}${rank_zero_records}" +) +file(WRITE + "${trace_base}.run-${run_id}-fixture.rank1.trace" + "${trace_header}${rank_one_records}" +) + +file(SHA256 "${fixture_root}/partition.txtp" partition_sha256) +file(SHA256 "${fixture_root}/oracle.patch" patch_sha256) +file( + SHA256 + "${trace_base}.run-${run_id}-fixture.rank0.trace" + rank_zero_sha256 +) +file( + SHA256 + "${trace_base}.run-${run_id}-fixture.rank1.trace" + rank_one_sha256 +) + +find_program(sort_executable NAMES sort REQUIRED) +file(WRITE "${fixture_root}/rank0.records" "${rank_zero_records}") +file(WRITE "${fixture_root}/rank1.records" "${rank_one_records}") +execute_process( + COMMAND + "${CMAKE_COMMAND}" -E env "LC_ALL=C" "${sort_executable}" + "${fixture_root}/rank0.records" "${fixture_root}/rank1.records" + OUTPUT_FILE "${fixture_root}/aggregate.records" + COMMAND_ERROR_IS_FATAL ANY +) +file(SHA256 "${fixture_root}/aggregate.records" aggregate_sha256) + +set(manifest [=[KaHIP focused MPI oracle fixture +upstream_revision=@UPSTREAM@ +instrumentation_patch=oracle.patch +instrumentation_patch_sha256=@PATCH_SHA@ + +tuple.graph=fixture.graph +tuple.ranks=2 +tuple.k=2 +tuple.preconfiguration=ultrafastmesh +tuple.seed=0 + +partition_sha256=@PARTITION_SHA@ +trace_format=kahip-mpi-trace-v3 +canonical_rank_aggregate_records=9 +canonical_rank_aggregate_sha256=@AGGREGATE_SHA@ +upstream_rank0_sha256=@RANK_ZERO_SHA@ +upstream_rank1_sha256=@RANK_ONE_SHA@ +candidate_rank0_sha256=@RANK_ZERO_SHA@ +candidate_rank1_sha256=@RANK_ONE_SHA@ + +stage.graph-distribution-node=1 +stage.graph-distribution-edge=1 +stage.contraction-label=1 +stage.quotient-node-weight=1 +stage.quotient-edge=1 +stage.projection-request=1 +stage.projection-reply=1 +stage.ghost-update=1 +stage.final-partition=1 +]=]) +string(REPLACE "@UPSTREAM@" "${upstream_revision}" manifest "${manifest}") +string(REPLACE "@PATCH_SHA@" "${patch_sha256}" manifest "${manifest}") +string( + REPLACE "@PARTITION_SHA@" "${partition_sha256}" manifest "${manifest}" +) +string( + REPLACE "@AGGREGATE_SHA@" "${aggregate_sha256}" manifest "${manifest}" +) +string( + REPLACE "@RANK_ZERO_SHA@" "${rank_zero_sha256}" manifest "${manifest}" +) +string( + REPLACE "@RANK_ONE_SHA@" "${rank_one_sha256}" manifest "${manifest}" +) +file(WRITE "${fixture_root}/oracle.txt" "${manifest}") + +function(run_verifier manifest_path patch_path result_output log_output) + execute_process( + COMMAND + "${CMAKE_COMMAND}" + "-DMANIFEST_PATH=${manifest_path}" + "-DPATCH_PATH=${patch_path}" + "-DREPOSITORY_ROOT=${fixture_root}" + "-DGRAPH_PATH=${fixture_root}/fixture.graph" + "-DPARTITION_PATH=${fixture_root}/partition.txtp" + "-DTRACE_BASE=${trace_base}" + "-DTRACE_RUN_ID=${run_id}" + "-DEXPECTED_RANKS=2" + "-DEXPECTED_K=2" + "-DEXPECTED_PRECONFIGURATION=ultrafastmesh" + "-DEXPECTED_SEED=0" + -P "${VERIFIER}" + RESULT_VARIABLE verifier_result + OUTPUT_VARIABLE verifier_output + ERROR_VARIABLE verifier_error + ) + set(${result_output} "${verifier_result}" PARENT_SCOPE) + set(${log_output} "${verifier_output}${verifier_error}" PARENT_SCOPE) +endfunction() + +run_verifier( + "${fixture_root}/oracle.txt" "${fixture_root}/oracle.patch" + valid_result valid_log +) +if(NOT valid_result EQUAL 0) + message(FATAL_ERROR "valid exact oracle fixture failed\n${valid_log}") +endif() + +string( + REPLACE + "candidate_rank0_sha256=${rank_zero_sha256}" + "candidate_rank0_sha256=0000000000000000000000000000000000000000000000000000000000000000" + wrong_rank_manifest + "${manifest}" +) +file(WRITE "${fixture_root}/wrong-rank.txt" "${wrong_rank_manifest}") +run_verifier( + "${fixture_root}/wrong-rank.txt" "${fixture_root}/oracle.patch" + wrong_rank_result wrong_rank_log +) +if(wrong_rank_result EQUAL 0 OR + NOT wrong_rank_log MATCHES "rank 0 trace SHA-256") + message(FATAL_ERROR + "rank-trace corruption did not fail closed\n${wrong_rank_log}" + ) +endif() + +file(WRITE + "${fixture_root}/duplicate.txt" + "${manifest}tuple.seed=0\n" +) +run_verifier( + "${fixture_root}/duplicate.txt" "${fixture_root}/oracle.patch" + duplicate_result duplicate_log +) +if(duplicate_result EQUAL 0 OR + NOT duplicate_log MATCHES "duplicate manifest key") + message(FATAL_ERROR + "duplicate manifest key did not fail closed\n${duplicate_log}" + ) +endif() + +file(MAKE_DIRECTORY "${fixture_root}/corrupt") +file(WRITE "${fixture_root}/corrupt/oracle.patch" "different patch\n") +run_verifier( + "${fixture_root}/oracle.txt" "${fixture_root}/corrupt/oracle.patch" + patch_result patch_log +) +if(patch_result EQUAL 0 OR + NOT patch_log MATCHES "instrumentation patch SHA-256") + message(FATAL_ERROR + "patch-provenance corruption did not fail closed\n${patch_log}" + ) +endif() + +string( + REPLACE + "canonical_rank_aggregate_sha256=${aggregate_sha256}" + "canonical_rank_aggregate_sha256=0000000000000000000000000000000000000000000000000000000000000000" + wrong_aggregate_manifest + "${manifest}" +) +file(WRITE "${fixture_root}/wrong-aggregate.txt" "${wrong_aggregate_manifest}") +run_verifier( + "${fixture_root}/wrong-aggregate.txt" "${fixture_root}/oracle.patch" + aggregate_result aggregate_log +) +if(aggregate_result EQUAL 0 OR + NOT aggregate_log MATCHES "canonical trace aggregate SHA-256") + message(FATAL_ERROR + "aggregate corruption did not fail closed\n${aggregate_log}" + ) +endif() diff --git a/parallel/parallel_src/tests/communication/mpi_trace_smoke.cmake b/parallel/parallel_src/tests/communication/mpi_trace_smoke.cmake new file mode 100644 index 00000000..01c58216 --- /dev/null +++ b/parallel/parallel_src/tests/communication/mpi_trace_smoke.cmake @@ -0,0 +1,76 @@ +if(NOT DEFINED PARHIP_EXECUTABLE OR NOT DEFINED MPIEXEC_EXECUTABLE OR + NOT DEFINED MPIEXEC_NUMPROC_FLAG OR NOT DEFINED GRAPH_PATH OR + NOT DEFINED TRACE_BASE) + message(FATAL_ERROR "MPI trace smoke is missing a required path") +endif() + +set(trace_run_id "smoke") +set(work_directory "${TRACE_BASE}.work") +set(partition_path "${work_directory}/tmppartition.txtp") +get_filename_component( + repository_root + "${CMAKE_CURRENT_LIST_DIR}/../../../.." + ABSOLUTE +) +set(oracle_directory + "${repository_root}/parallel/parallel_src/tests/fixtures/mpi_trace_oracle" +) +set(oracle_manifest "${oracle_directory}/task-5-oracle-golden.txt") +set(oracle_patch "${oracle_directory}/task-5-upstream-trace.patch") +set(oracle_verifier + "${CMAKE_CURRENT_LIST_DIR}/verify_mpi_trace_oracle.cmake" +) + +file(MAKE_DIRECTORY "${work_directory}") +file(REMOVE "${partition_path}") +file(GLOB stale_trace_files + "${TRACE_BASE}.run-${trace_run_id}-*.rank*.trace" +) +file(REMOVE ${stale_trace_files}) +execute_process( + COMMAND + "${CMAKE_COMMAND}" -E env + "KAHIP_MPI_TRACE_PATH=${TRACE_BASE}" + "KAHIP_MPI_TRACE_RUN_ID=${trace_run_id}" + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 2 + ${MPIEXEC_PREFLAGS} + "${PARHIP_EXECUTABLE}" + ${MPIEXEC_POSTFLAGS} + "${GRAPH_PATH}" + --k=2 --preconfiguration=ultrafastmesh --seed=0 --save_partition + WORKING_DIRECTORY "${work_directory}" + RESULT_VARIABLE run_result + OUTPUT_VARIABLE run_output + ERROR_VARIABLE run_error +) +if(NOT run_result EQUAL 0) + message(FATAL_ERROR + "trace smoke failed (${run_result})\n${run_output}\n${run_error}" + ) +endif() + +execute_process( + COMMAND + "${CMAKE_COMMAND}" + "-DMANIFEST_PATH=${oracle_manifest}" + "-DPATCH_PATH=${oracle_patch}" + "-DREPOSITORY_ROOT=${repository_root}" + "-DGRAPH_PATH=${GRAPH_PATH}" + "-DPARTITION_PATH=${partition_path}" + "-DTRACE_BASE=${TRACE_BASE}" + "-DTRACE_RUN_ID=${trace_run_id}" + "-DEXPECTED_RANKS=2" + "-DEXPECTED_K=2" + "-DEXPECTED_PRECONFIGURATION=ultrafastmesh" + "-DEXPECTED_SEED=0" + -P "${oracle_verifier}" + RESULT_VARIABLE verifier_result + OUTPUT_VARIABLE verifier_output + ERROR_VARIABLE verifier_error +) +if(NOT verifier_result EQUAL 0) + message(FATAL_ERROR + "exact MPI oracle verification failed (${verifier_result})\n" + "${verifier_output}${verifier_error}" + ) +endif() diff --git a/parallel/parallel_src/tests/communication/mpi_trace_test.cpp b/parallel/parallel_src/tests/communication/mpi_trace_test.cpp new file mode 100644 index 00000000..db3d3e0f --- /dev/null +++ b/parallel/parallel_src/tests/communication/mpi_trace_test.cpp @@ -0,0 +1,148 @@ +#include +#include +#include +#include + +#include + +#include "communication/mpi_trace.h" + +TEST_CASE("MPI trace records are canonical and byte comparable", + "[mpi][trace]") { + using parhip::mpi::trace::epoch; + using parhip::mpi::trace::hierarchy_position; + using parhip::mpi::trace::record; + auto const input = hierarchy_position{ + .cycle = 0, + .level = 0, + .epoch_id = epoch::input, + .iteration = 0, + .round = 0}; + auto const contraction = hierarchy_position{ + .cycle = 0, + .level = 2, + .epoch_id = epoch::contraction, + .iteration = 0, + .round = 0}; + auto const projection = hierarchy_position{ + .cycle = 0, + .level = 2, + .epoch_id = epoch::projection, + .iteration = 0, + .round = 0}; + auto const final = hierarchy_position{ + .cycle = 0, + .level = 0, + .epoch_id = epoch::final_partition, + .iteration = 0, + .round = 0}; + auto records = std::vector{ + parhip::mpi::trace::final_partition(final, 9, 1, 1), + parhip::mpi::trace::projection_reply(projection, 3, 0, 1, 8, 41), + parhip::mpi::trace::graph_distribution_edge(input, 7, 1, 8, 4), + parhip::mpi::trace::quotient_edge(contraction, 4, 1, 2, 5), + parhip::mpi::trace::graph_distribution_node(input, 7, 1, 3), + parhip::mpi::trace::projection_request(projection, 3, 0, 1, 8), + parhip::mpi::trace::contraction_label(contraction, 7, 1, 19, 4), + parhip::mpi::trace::ghost_update(projection, 8, 1, 0, 41), + parhip::mpi::trace::quotient_node_weight(contraction, 4, 1, 6), + parhip::mpi::trace::block_propagation(contraction, 4, 1, 1, 1)}; + + auto const expected = std::string{ + "kahip-mpi-trace-v3 upstream=" + "5935f349f65f1788a9b68fcf6d853e698d86956d\n" + "graph-distribution-node cycle=0 level=0 epoch=input iteration=0 round=0 global=7 " + "owner=1 requester=- receiver=1 key=owner:1 weight=3\n" + "graph-distribution-edge cycle=0 level=0 epoch=input iteration=0 round=0 global=7 " + "owner=1 requester=- receiver=1 key=target:8 weight=4\n" + "contraction-label cycle=0 level=2 epoch=contraction iteration=0 round=0 global=7 " + "owner=1 requester=- receiver=1 key=label:19 coarse=4\n" + "quotient-node-weight cycle=0 level=2 epoch=contraction iteration=0 round=0 global=4 " + "owner=1 requester=- receiver=1 key=node weight=6\n" + "quotient-edge cycle=0 level=2 epoch=contraction iteration=0 round=0 global=4 " + "owner=1 requester=- receiver=1 key=target:2 weight=5\n" + "projection-request cycle=0 level=2 epoch=projection iteration=0 round=0 global=8 " + "owner=1 requester=0 receiver=1 key=request:3 requester=0 owner=1\n" + "projection-reply cycle=0 level=2 epoch=projection iteration=0 round=0 global=8 " + "owner=1 requester=0 receiver=0 key=request:3 requester=0 owner=1 " + "label=41\n" + "ghost-update cycle=0 level=2 epoch=projection iteration=0 round=0 global=8 " + "owner=1 requester=- receiver=0 key=label label=41\n" + "block-propagation cycle=0 level=2 epoch=contraction iteration=0 round=0 global=4 " + "owner=1 requester=- receiver=1 key=block block=1\n" + "final-partition cycle=0 level=0 epoch=final-partition iteration=0 round=0 global=9 " + "owner=1 requester=- receiver=1 key=partition block=1\n"}; + + REQUIRE(parhip::mpi::trace::canonical_text(records) == expected); + + std::ranges::reverse(records); + REQUIRE(parhip::mpi::trace::canonical_text(records) == expected); +} + +TEST_CASE("MPI trace exposes every Task 5 stage schema", "[mpi][trace]") { + constexpr auto expected = std::array{ + parhip::mpi::trace::stage::graph_distribution_node, + parhip::mpi::trace::stage::graph_distribution_edge, + parhip::mpi::trace::stage::contraction_label, + parhip::mpi::trace::stage::quotient_node_weight, + parhip::mpi::trace::stage::quotient_edge, + parhip::mpi::trace::stage::projection_request, + parhip::mpi::trace::stage::projection_reply, + parhip::mpi::trace::stage::ghost_update, + parhip::mpi::trace::stage::block_propagation, + parhip::mpi::trace::stage::final_partition}; + + STATIC_REQUIRE(parhip::mpi::trace::all_stages == expected); +} + +TEST_CASE("MPI trace distinguishes hierarchy levels and semantic receivers", + "[mpi][trace]") { + using parhip::mpi::trace::epoch; + using parhip::mpi::trace::hierarchy_position; + + auto const level_one = hierarchy_position{ + .cycle = 1, + .level = 1, + .epoch_id = epoch::projection, + .iteration = 0, + .round = 0}; + auto const level_two = hierarchy_position{ + .cycle = 1, + .level = 2, + .epoch_id = epoch::projection, + .iteration = 0, + .round = 0}; + auto const records = std::vector{ + parhip::mpi::trace::quotient_node_weight(level_two, 9, 0, 7), + parhip::mpi::trace::quotient_node_weight(level_one, 9, 0, 7), + parhip::mpi::trace::ghost_update(level_two, 9, 1, 2, 7), + parhip::mpi::trace::ghost_update(level_two, 9, 1, 0, 7)}; + + auto const expected = std::string{ + "kahip-mpi-trace-v3 upstream=" + "5935f349f65f1788a9b68fcf6d853e698d86956d\n" + "quotient-node-weight cycle=1 level=1 epoch=projection iteration=0 round=0 " + "global=9 owner=0 requester=- receiver=0 key=node weight=7\n" + "quotient-node-weight cycle=1 level=2 epoch=projection iteration=0 round=0 " + "global=9 owner=0 requester=- receiver=0 key=node weight=7\n" + "ghost-update cycle=1 level=2 epoch=projection iteration=0 round=0 global=9 " + "owner=1 requester=- receiver=0 key=label label=7\n" + "ghost-update cycle=1 level=2 epoch=projection iteration=0 round=0 global=9 " + "owner=1 requester=- receiver=2 key=label label=7\n"}; + + REQUIRE(parhip::mpi::trace::canonical_text(records) == expected); +} + +TEST_CASE("MPI trace run IDs make rank filenames collision-resistant", + "[mpi][trace]") { + auto const slash = + parhip::mpi::trace::rank_file_path("trace/output", "job/42", 3); + auto const question = + parhip::mpi::trace::rank_file_path("trace/output", "job?42", 3); + + REQUIRE(slash == + parhip::mpi::trace::rank_file_path("trace/output", "job/42", 3)); + REQUIRE(slash != question); + REQUIRE(slash.starts_with("trace/output.run-job_42-")); + REQUIRE(slash.ends_with(".rank3.trace")); +} diff --git a/parallel/parallel_src/tests/communication/mpi_trace_writer_mpi_test.cpp b/parallel/parallel_src/tests/communication/mpi_trace_writer_mpi_test.cpp new file mode 100644 index 00000000..56525b8d --- /dev/null +++ b/parallel/parallel_src/tests/communication/mpi_trace_writer_mpi_test.cpp @@ -0,0 +1,158 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "communication/mpi_trace.h" + +namespace { +constexpr auto run_id = std::string_view{"writer-fixture"}; +constexpr auto path_mismatch_error = + std::string_view{"MPI trace path differs across communicator ranks"}; + +[[nodiscard]] auto rank() -> int { + int result = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &result) == MPI_SUCCESS); + return result; +} + +void require_two_ranks() { + int size = 0; + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE(size == 2); +} + +[[nodiscard]] auto test_base(std::string_view suffix) -> std::string { + auto const* root = std::getenv("KAHIP_TRACE_WRITER_TEST_BASE"); + REQUIRE(root != nullptr); + return std::string{root} + "-" + std::string{suffix}; +} + +[[nodiscard]] auto rank_files(std::vector const& bases) + -> std::vector { + auto files = std::vector{}; + for (auto const& base : bases) { + for (auto target_rank = 0; target_rank < 2; ++target_rank) { + files.push_back(parhip::mpi::trace::rank_file_path( + base, run_id, target_rank)); + } + } + return files; +} + +void remove_files(std::vector const& files, int local_rank) { + if (local_rank == 0) { + for (auto const& file : files) { + static_cast(std::filesystem::remove(file)); + } + } + REQUIRE(MPI_Barrier(MPI_COMM_WORLD) == MPI_SUCCESS); +} + +void set_common_run_id() { + REQUIRE(setenv("KAHIP_MPI_TRACE_RUN_ID", run_id.data(), 1) == 0); +} + +[[nodiscard]] auto write_and_capture_error() -> std::string { + parhip::mpi::trace::reset(); + try { + parhip::mpi::trace::write_rank_file_if_requested(MPI_COMM_WORLD); + } catch (std::runtime_error const& error) { + return error.what(); + } + return {}; +} + +void check_no_files(std::vector const& files) { + for (auto const& file : files) { + CHECK_FALSE(std::filesystem::exists(file)); + } +} +} // namespace + +TEST_CASE("trace writer rejects rank-local enablement mismatch", + "[mpi][trace][writer][path]") { + require_two_ranks(); + auto const local_rank = rank(); + auto const base = test_base("presence-mismatch"); + auto const files = rank_files({base}); + remove_files(files, local_rank); + set_common_run_id(); + if (local_rank == 0) { + REQUIRE(unsetenv("KAHIP_MPI_TRACE_PATH") == 0); + } else { + REQUIRE(setenv("KAHIP_MPI_TRACE_PATH", base.c_str(), 1) == 0); + } + + auto const error = write_and_capture_error(); + CHECK(error == path_mismatch_error); + REQUIRE(MPI_Barrier(MPI_COMM_WORLD) == MPI_SUCCESS); + check_no_files(files); + remove_files(files, local_rank); +} + +TEST_CASE("trace writer rejects different rank-local base paths", + "[mpi][trace][writer][path]") { + require_two_ranks(); + auto const local_rank = rank(); + auto const first = test_base("path-a"); + auto const second = test_base("path-b"); + auto const files = rank_files({first, second}); + remove_files(files, local_rank); + set_common_run_id(); + auto const& local_path = local_rank == 0 ? first : second; + REQUIRE(setenv("KAHIP_MPI_TRACE_PATH", local_path.c_str(), 1) == 0); + + auto const error = write_and_capture_error(); + CHECK(error == path_mismatch_error); + REQUIRE(MPI_Barrier(MPI_COMM_WORLD) == MPI_SUCCESS); + check_no_files(files); + remove_files(files, local_rank); +} + +TEST_CASE("trace writer returns collectively when tracing is unset", + "[mpi][trace][writer][path]") { + require_two_ranks(); + auto const local_rank = rank(); + auto const base = test_base("all-unset"); + auto const files = rank_files({base}); + remove_files(files, local_rank); + set_common_run_id(); + REQUIRE(unsetenv("KAHIP_MPI_TRACE_PATH") == 0); + + CHECK(write_and_capture_error().empty()); + REQUIRE(MPI_Barrier(MPI_COMM_WORLD) == MPI_SUCCESS); + check_no_files(files); + remove_files(files, local_rank); +} + +TEST_CASE("trace writer uses one agreed base path", + "[mpi][trace][writer][path]") { + require_two_ranks(); + auto const local_rank = rank(); + auto const base = test_base("common"); + auto const files = rank_files({base}); + remove_files(files, local_rank); + set_common_run_id(); + REQUIRE(setenv("KAHIP_MPI_TRACE_PATH", base.c_str(), 1) == 0); + + CHECK(write_and_capture_error().empty()); + REQUIRE(MPI_Barrier(MPI_COMM_WORLD) == MPI_SUCCESS); + auto const expected = + std::string{"kahip-mpi-trace-v3 upstream=" + "5935f349f65f1788a9b68fcf6d853e698d86956d\n"}; + for (auto const& file : files) { + REQUIRE(std::filesystem::exists(file)); + auto input = std::ifstream{file, std::ios::binary}; + auto contents = std::string{std::istreambuf_iterator{input}, {}}; + CHECK(contents == expected); + } + remove_files(files, local_rank); +} diff --git a/parallel/parallel_src/tests/communication/population_size_broadcast_failure_probe.cpp b/parallel/parallel_src/tests/communication/population_size_broadcast_failure_probe.cpp new file mode 100644 index 00000000..6576588a --- /dev/null +++ b/parallel/parallel_src/tests/communication/population_size_broadcast_failure_probe.cpp @@ -0,0 +1,130 @@ +#include +#include + +#include +#include + +#include "parallel_mh/population_size_broadcast.h" + +// KAHIP_PMPI_CALLBACK_REGION_BEGIN +namespace population_failure_probe { +inline bool active = false; +inline MPI_Comm expected_communicator = MPI_COMM_NULL; +inline int broadcasts = 0; +inline int point_to_point_calls = 0; +inline int barriers = 0; +inline bool callback_error = false; + +void write_text(std::string_view text) noexcept { + static_cast(::write(STDERR_FILENO, text.data(), text.size())); +} + +[[noreturn]] void observed_abort(MPI_Comm communicator, + int error_code) noexcept { + int relation = MPI_UNEQUAL; + if (expected_communicator == MPI_COMM_NULL || error_code != EXIT_FAILURE || + PMPI_Comm_compare(communicator, expected_communicator, &relation) != + MPI_SUCCESS || + relation != MPI_IDENT || broadcasts != 1 || point_to_point_calls != 0 || + barriers != 0 || callback_error) { + write_text("observed population-size MPI_Abort with unexpected state\n"); + std::_Exit(91); + } + write_text( + "observed population-size MPI_Abort on the affected subcommunicator\n"); + std::_Exit(86); +} +} // namespace population_failure_probe + +static_assert(noexcept(population_failure_probe::write_text({}))); +static_assert(noexcept(population_failure_probe::observed_abort(MPI_COMM_NULL, + 0))); + +extern "C" int MPI_Bcast(void* buffer, + int count, + MPI_Datatype datatype, + int root, + MPI_Comm communicator) { + if (!population_failure_probe::active) { + return PMPI_Bcast(buffer, count, datatype, root, communicator); + } + ++population_failure_probe::broadcasts; + int relation = MPI_UNEQUAL; + if (count != 1 || datatype != MPI_INT || root != 0 || + PMPI_Comm_compare(communicator, + population_failure_probe::expected_communicator, + &relation) != MPI_SUCCESS || + relation != MPI_IDENT) { + population_failure_probe::callback_error = true; + } + return MPI_ERR_OTHER; +} + +extern "C" int MPI_Isend(void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator, + MPI_Request* request) { + if (population_failure_probe::active) { + ++population_failure_probe::point_to_point_calls; + } + return PMPI_Isend(buffer, count, datatype, destination, tag, communicator, + request); +} + +extern "C" int MPI_Recv(void* buffer, + int count, + MPI_Datatype datatype, + int source, + int tag, + MPI_Comm communicator, + MPI_Status* status) { + if (population_failure_probe::active) { + ++population_failure_probe::point_to_point_calls; + } + return PMPI_Recv(buffer, count, datatype, source, tag, communicator, status); +} + +extern "C" int MPI_Barrier(MPI_Comm communicator) { + if (population_failure_probe::active) { + ++population_failure_probe::barriers; + } + return PMPI_Barrier(communicator); +} + +extern "C" int MPI_Abort(MPI_Comm communicator, int error_code) { + population_failure_probe::observed_abort(communicator, error_code); +} +// KAHIP_PMPI_CALLBACK_REGION_END + +int main(int argc, char** argv) { + if (MPI_Init(&argc, &argv) != MPI_SUCCESS) { + return 2; + } + auto world_rank = 0; + auto world_size = 0; + if (MPI_Comm_rank(MPI_COMM_WORLD, &world_rank) != MPI_SUCCESS || + MPI_Comm_size(MPI_COMM_WORLD, &world_size) != MPI_SUCCESS) { + return 3; + } + + auto communicator = MPI_COMM_NULL; + if (MPI_Comm_split(MPI_COMM_WORLD, 0, world_size - world_rank, + &communicator) != MPI_SUCCESS || + communicator == MPI_COMM_NULL) { + return 4; + } + if (MPI_Comm_set_errhandler(communicator, MPI_ERRORS_RETURN) != MPI_SUCCESS) { + return 5; + } + population_failure_probe::expected_communicator = communicator; + population_failure_probe::active = true; + + static_cast(kahip::parallel_mh::broadcast_population_size( + communicator, world_rank == world_size - 1 ? 17 : -1, false)); + population_failure_probe::write_text( + "population-size broadcast returned without fail-fast\n"); + std::_Exit(92); +} diff --git a/parallel/parallel_src/tests/communication/population_size_broadcast_mpi_test.cpp b/parallel/parallel_src/tests/communication/population_size_broadcast_mpi_test.cpp new file mode 100644 index 00000000..59173397 --- /dev/null +++ b/parallel/parallel_src/tests/communication/population_size_broadcast_mpi_test.cpp @@ -0,0 +1,173 @@ +#include + +#include + +#include +#include + +#include "parallel_mh/population_size_broadcast.h" + +// KAHIP_PMPI_CALLBACK_REGION_BEGIN +namespace population_size_probe { +struct counters final { + int broadcasts = 0; + int point_to_point_calls = 0; + int barriers = 0; + bool callback_error = false; +}; + +inline bool active = false; +inline MPI_Comm expected_communicator = MPI_COMM_NULL; +inline counters observed{}; + +void reset(MPI_Comm communicator) noexcept { + expected_communicator = communicator; + observed = {}; +} + +void observe_broadcast(int count, + MPI_Datatype datatype, + int root, + MPI_Comm communicator) noexcept { + if (!active) { + return; + } + ++observed.broadcasts; + int relation = MPI_UNEQUAL; + if (expected_communicator == MPI_COMM_NULL || count != 1 || + datatype != MPI_INT || root != 0 || + PMPI_Comm_compare(communicator, expected_communicator, &relation) != + MPI_SUCCESS || + relation != MPI_IDENT) { + observed.callback_error = true; + } +} + +class activation final { + public: + explicit activation(MPI_Comm communicator) noexcept { + reset(communicator); + active = true; + } + + ~activation() noexcept { active = false; } + + activation(activation const&) = delete; + auto operator=(activation const&) -> activation& = delete; +}; +} // namespace population_size_probe + +static_assert(noexcept(population_size_probe::reset(MPI_COMM_NULL))); +static_assert( + noexcept(population_size_probe::observe_broadcast(0, + MPI_DATATYPE_NULL, + 0, + MPI_COMM_NULL))); + +extern "C" int MPI_Bcast(void* buffer, + int count, + MPI_Datatype datatype, + int root, + MPI_Comm communicator) { + population_size_probe::observe_broadcast(count, datatype, root, communicator); + return PMPI_Bcast(buffer, count, datatype, root, communicator); +} + +extern "C" int MPI_Isend(void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator, + MPI_Request* request) { + if (population_size_probe::active) { + ++population_size_probe::observed.point_to_point_calls; + } + return PMPI_Isend(buffer, count, datatype, destination, tag, communicator, + request); +} + +extern "C" int MPI_Recv(void* buffer, + int count, + MPI_Datatype datatype, + int source, + int tag, + MPI_Comm communicator, + MPI_Status* status) { + if (population_size_probe::active) { + ++population_size_probe::observed.point_to_point_calls; + } + return PMPI_Recv(buffer, count, datatype, source, tag, communicator, status); +} + +extern "C" int MPI_Barrier(MPI_Comm communicator) { + if (population_size_probe::active) { + ++population_size_probe::observed.barriers; + } + return PMPI_Barrier(communicator); +} +// KAHIP_PMPI_CALLBACK_REGION_END + +namespace { +struct population_case final { + int root_estimate; + bool easy_construction; + int expected; +}; + +constexpr auto rank_cases = std::array{ + population_case{.root_estimate = -7, + .easy_construction = false, + .expected = 3}, + population_case{.root_estimate = 17, + .easy_construction = false, + .expected = 17}, + population_case{.root_estimate = 137, + .easy_construction = false, + .expected = 100}, + population_case{.root_estimate = 73, + .easy_construction = true, + .expected = 50}, + population_case{.root_estimate = 50, + .easy_construction = true, + .expected = 50}, +}; +} // namespace + +TEST_CASE( + "population size uses one communicator-scoped broadcast and exact clamp") { + auto world_rank = 0; + auto world_size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &world_rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &world_size) == MPI_SUCCESS); + REQUIRE(world_size >= 1); + REQUIRE(world_size <= static_cast(rank_cases.size())); + + auto communicator = MPI_COMM_NULL; + REQUIRE(MPI_Comm_split(MPI_COMM_WORLD, 0, world_size - world_rank, + &communicator) == MPI_SUCCESS); + REQUIRE(communicator != MPI_COMM_NULL); + + auto communicator_rank = -1; + REQUIRE(MPI_Comm_rank(communicator, &communicator_rank) == MPI_SUCCESS); + auto const test_case = rank_cases[static_cast(world_size - 1)]; + auto const local_value = communicator_rank == 0 + ? test_case.root_estimate + : std::numeric_limits::min(); + + int actual = 0; + population_size_probe::counters observed{}; + { + population_size_probe::activation const probe{communicator}; + actual = kahip::parallel_mh::broadcast_population_size( + communicator, local_value, test_case.easy_construction); + observed = population_size_probe::observed; + } + + REQUIRE(MPI_Comm_free(&communicator) == MPI_SUCCESS); + REQUIRE(actual == test_case.expected); + REQUIRE(observed.broadcasts == 1); + REQUIRE(observed.point_to_point_calls == 0); + REQUIRE(observed.barriers == 0); + REQUIRE_FALSE(observed.callback_error); +} diff --git a/parallel/parallel_src/tests/communication/verify_evolutionary_collectives_failure.cmake b/parallel/parallel_src/tests/communication/verify_evolutionary_collectives_failure.cmake new file mode 100644 index 00000000..4e53e4d7 --- /dev/null +++ b/parallel/parallel_src/tests/communication/verify_evolutionary_collectives_failure.cmake @@ -0,0 +1,59 @@ +cmake_minimum_required(VERSION 4.0) + +if( + NOT DEFINED MPIEXEC_EXECUTABLE + OR NOT DEFINED MPIEXEC_NUMPROC_FLAG + OR NOT DEFINED PROBE + OR NOT DEFINED MODE + OR NOT DEFINED EXPECTED_DIAGNOSTIC +) + message( + FATAL_ERROR + "MPI launcher, PROBE, MODE, and EXPECTED_DIAGNOSTIC are required" + ) +endif() + +execute_process( + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 2 + ${MPIEXEC_PREFLAGS} + "${PROBE}" "${MODE}" + ${MPIEXEC_POSTFLAGS} + RESULT_VARIABLE probe_result + OUTPUT_VARIABLE probe_stdout + ERROR_VARIABLE probe_stderr + TIMEOUT 8 +) + +set(probe_output "${probe_stdout}\n${probe_stderr}") +if("${probe_result}" STREQUAL "0") + message(FATAL_ERROR "failure probe returned success\n${probe_output}") +endif() +if("${probe_result}" MATCHES "[Tt]imeout") + message(FATAL_ERROR "failure probe timed out\n${probe_output}") +endif() + +string( + FIND + "${probe_output}" + "observed evolutionary MPI_Abort on affected communicator" + abort_offset +) +if(abort_offset EQUAL -1) + message(FATAL_ERROR "missing affected-communicator abort\n${probe_output}") +endif() + +string(FIND "${probe_output}" "${EXPECTED_DIAGNOSTIC}" diagnostic_offset) +if(diagnostic_offset EQUAL -1) + message( + FATAL_ERROR + "missing '${EXPECTED_DIAGNOSTIC}' diagnostic\n${probe_output}" + ) +endif() + +foreach(forbidden IN ITEMS "unexpected state" "returned without fail-fast") + string(FIND "${probe_output}" "${forbidden}" forbidden_offset) + if(NOT forbidden_offset EQUAL -1) + message(FATAL_ERROR "failure probe used ${forbidden}\n${probe_output}") + endif() +endforeach() diff --git a/parallel/parallel_src/tests/communication/verify_mpi_async_neighbors_failure.cmake b/parallel/parallel_src/tests/communication/verify_mpi_async_neighbors_failure.cmake new file mode 100644 index 00000000..9fb763fa --- /dev/null +++ b/parallel/parallel_src/tests/communication/verify_mpi_async_neighbors_failure.cmake @@ -0,0 +1,106 @@ +if( + NOT DEFINED MPIEXEC_EXECUTABLE + OR NOT DEFINED MPIEXEC_NUMPROC_FLAG + OR NOT DEFINED PROBE + OR NOT DEFINED MODE + OR NOT DEFINED ABORT_KIND + OR NOT DEFINED EXPECTED_DIAGNOSTIC +) + message( + FATAL_ERROR + "MPI launcher, PROBE, MODE, ABORT_KIND, and EXPECTED_DIAGNOSTIC are required" + ) +endif() + +execute_process( + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 1 + ${MPIEXEC_PREFLAGS} + "${PROBE}" "${MODE}" + ${MPIEXEC_POSTFLAGS} + RESULT_VARIABLE probe_result + OUTPUT_VARIABLE probe_stdout + ERROR_VARIABLE probe_stderr + TIMEOUT 8 +) + +if("${probe_result}" STREQUAL "0") + message( + FATAL_ERROR + "async neighborhood failure probe returned success unexpectedly\nstdout:\n${probe_stdout}\nstderr:\n${probe_stderr}" + ) +endif() + +set(probe_output "${probe_stdout}\n${probe_stderr}") +string(FIND "${probe_output}" "${EXPECTED_DIAGNOSTIC}" diagnostic_offset) +if(diagnostic_offset EQUAL -1) + message( + FATAL_ERROR + "missing fail-fast diagnostic '${EXPECTED_DIAGNOSTIC}' for ${MODE}\n${probe_output}" + ) +endif() + +if(ABORT_KIND STREQUAL "mpi") + string(FIND + "${probe_output}" + "observed MPI_Abort for ${MODE} on operation communicator" + abort_offset + ) + if(abort_offset EQUAL -1) + message( + FATAL_ERROR + "missing operation-communicator abort marker for ${MODE}\n${probe_output}" + ) + endif() +elseif(ABORT_KIND STREQUAL "raw") + string(FIND + "${probe_output}" + "observed SIGABRT for ${MODE} raw-abort path" + abort_offset + ) + if(abort_offset EQUAL -1) + message( + FATAL_ERROR + "missing raw-abort marker for ${MODE}\n${probe_output}" + ) + endif() + string(FIND "${probe_output}" "observed MPI_Abort" mpi_abort_offset) + if(NOT mpi_abort_offset EQUAL -1) + message( + FATAL_ERROR + "raw-abort path called MPI_Abort for ${MODE}\n${probe_output}" + ) + endif() +else() + message(FATAL_ERROR "unknown ABORT_KIND: ${ABORT_KIND}") +endif() + +if(DEFINED EXPECT_INJECTED_MPI_ERROR AND EXPECT_INJECTED_MPI_ERROR) + string(REGEX MATCH + "injecting raw MPI error ([0-9]+) for ${MODE}" + injection_marker + "${probe_output}" + ) + if(NOT injection_marker) + message( + FATAL_ERROR + "missing injected MPI error marker for ${MODE}\n${probe_output}" + ) + endif() + set(injected_code "${CMAKE_MATCH_1}") + string(FIND "${probe_output}" "MPI error ${injected_code}" code_offset) + if(code_offset EQUAL -1) + message( + FATAL_ERROR + "diagnostic did not retain injected MPI error ${injected_code} for ${MODE}\n${probe_output}" + ) + endif() +endif() + +string(FIND "${probe_output}" "forbidden cleanup" forbidden_offset) +if(NOT forbidden_offset EQUAL -1) + message( + FATAL_ERROR + "cleanup recursion or forbidden MPI call observed for ${MODE}\n${probe_output}" + ) +endif() diff --git a/parallel/parallel_src/tests/communication/verify_mpi_fail_fast_probe.cmake b/parallel/parallel_src/tests/communication/verify_mpi_fail_fast_probe.cmake new file mode 100644 index 00000000..8c052551 --- /dev/null +++ b/parallel/parallel_src/tests/communication/verify_mpi_fail_fast_probe.cmake @@ -0,0 +1,113 @@ +cmake_minimum_required(VERSION 4.0) + +if( + NOT DEFINED PROBE + OR NOT DEFINED MODE + OR NOT DEFINED EXPECT_FAILURE + OR NOT DEFINED EXPECTED_MARKER +) + message( + FATAL_ERROR + "PROBE, MODE, EXPECT_FAILURE, and EXPECTED_MARKER are required" + ) +endif() + +if(DEFINED MPI_RANKS AND MPI_RANKS GREATER 0) + if(NOT DEFINED MPIEXEC_EXECUTABLE OR NOT DEFINED MPIEXEC_NUMPROC_FLAG) + message(FATAL_ERROR "MPI launcher variables are required") + endif() + execute_process( + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" "${MPI_RANKS}" + ${MPIEXEC_PREFLAGS} "${PROBE}" "${MODE}" ${MPIEXEC_POSTFLAGS} + RESULT_VARIABLE probe_result + OUTPUT_VARIABLE probe_stdout + ERROR_VARIABLE probe_stderr + TIMEOUT 8 + ) +else() + execute_process( + COMMAND "${PROBE}" "${MODE}" + RESULT_VARIABLE probe_result + OUTPUT_VARIABLE probe_stdout + ERROR_VARIABLE probe_stderr + TIMEOUT 8 + ) +endif() + +set(probe_output "${probe_stdout}\n${probe_stderr}") + +if(EXPECT_FAILURE) + if("${probe_result}" STREQUAL "0") + message(FATAL_ERROR "failure probe returned successfully\n${probe_output}") + endif() +else() + if(NOT "${probe_result}" STREQUAL "0") + message( + FATAL_ERROR + "success control failed (result=${probe_result})\n${probe_output}" + ) + endif() +endif() + +foreach( + forbidden_literal + IN ITEMS + "forbidden MPI call" + "MPI_Comm_free(tracked duplicate)" + "cleanup-attempts=1" + "returned-from-failure" + "misleading safe observer profile" + "Signal: Aborted" + "SIGABRT" +) + string(FIND "${probe_output}" "${forbidden_literal}" forbidden_index) + if(NOT forbidden_index EQUAL -1) + message(FATAL_ERROR "${probe_output}") + endif() +endforeach() + +foreach( + required_literal + IN ITEMS EXPECTED_DIAGNOSTIC EXPECTED_DETAIL EXPECTED_INJECTION +) + if(DEFINED ${required_literal} AND NOT "${${required_literal}}" STREQUAL "") + string(FIND "${probe_output}" "${${required_literal}}" required_index) + if(required_index EQUAL -1) + message( + FATAL_ERROR + "missing required output '${${required_literal}}'\n${probe_output}" + ) + endif() + endif() +endforeach() + +function(require_literal_once required_marker) + set(remaining_output "${probe_output}") + set(marker_count 0) + string(LENGTH "${required_marker}" marker_length) + while(TRUE) + string(FIND "${remaining_output}" "${required_marker}" marker_index) + if(marker_index EQUAL -1) + break() + endif() + math(EXPR marker_count "${marker_count} + 1") + math(EXPR remaining_begin "${marker_index} + ${marker_length}") + string(SUBSTRING "${remaining_output}" ${remaining_begin} -1 remaining_output) + endwhile() + if(NOT marker_count EQUAL 1) + message( + FATAL_ERROR + "expected exactly one '${required_marker}', found ${marker_count}\n${probe_output}" + ) + endif() +endfunction() + +if(EXPECT_FAILURE AND DEFINED MPI_RANKS AND MPI_RANKS GREATER 0) + math(EXPR last_rank "${MPI_RANKS} - 1") + foreach(rank RANGE 0 ${last_rank}) + require_literal_once("observed MPI_Abort rank=${rank} ${EXPECTED_MARKER}") + endforeach() +else() + require_literal_once("${EXPECTED_MARKER}") +endif() diff --git a/parallel/parallel_src/tests/communication/verify_mpi_fixed_broadcast_failure.cmake b/parallel/parallel_src/tests/communication/verify_mpi_fixed_broadcast_failure.cmake new file mode 100644 index 00000000..2d9c44fc --- /dev/null +++ b/parallel/parallel_src/tests/communication/verify_mpi_fixed_broadcast_failure.cmake @@ -0,0 +1,72 @@ +cmake_minimum_required(VERSION 4.0) + +if( + NOT DEFINED MPIEXEC_EXECUTABLE + OR NOT DEFINED MPIEXEC_NUMPROC_FLAG + OR NOT DEFINED PROBE + OR NOT DEFINED MODE + OR NOT DEFINED EXPECTED_DIAGNOSTIC + OR NOT DEFINED FIXTURE +) + message( + FATAL_ERROR + "MPI launcher, PROBE, MODE, EXPECTED_DIAGNOSTIC, and FIXTURE are required" + ) +endif() + +file(REMOVE "${FIXTURE}") +execute_process( + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 2 + ${MPIEXEC_PREFLAGS} + "${PROBE}" + ${MPIEXEC_POSTFLAGS} + "${MODE}" "${FIXTURE}" + RESULT_VARIABLE probe_result + OUTPUT_VARIABLE probe_stdout + ERROR_VARIABLE probe_stderr + TIMEOUT 8 +) +file(REMOVE "${FIXTURE}") + +set(probe_output "${probe_stdout}\n${probe_stderr}") +if("${probe_result}" STREQUAL "0") + message(FATAL_ERROR "failure probe returned success\n${probe_output}") +endif() +if("${probe_result}" MATCHES "[Tt]imeout") + message(FATAL_ERROR "failure probe timed out\n${probe_output}") +endif() + +string( + REGEX MATCHALL + "observed fixed-broadcast MPI_Abort on affected communicator" + abort_markers + "${probe_output}" +) +list(LENGTH abort_markers abort_count) +if(NOT abort_count EQUAL 2) + message( + FATAL_ERROR + "expected exactly 2 affected-communicator abort markers; found ${abort_count}\n${probe_output}" + ) +endif() + +string(FIND "${probe_output}" "${EXPECTED_DIAGNOSTIC}" diagnostic_offset) +if(diagnostic_offset EQUAL -1) + message( + FATAL_ERROR + "missing '${EXPECTED_DIAGNOSTIC}' diagnostic\n${probe_output}" + ) +endif() + +foreach( + forbidden + IN ITEMS + "unexpected state" + "returned without fail-fast" +) + string(FIND "${probe_output}" "${forbidden}" forbidden_offset) + if(NOT forbidden_offset EQUAL -1) + message(FATAL_ERROR "failure probe used ${forbidden}\n${probe_output}") + endif() +endforeach() diff --git a/parallel/parallel_src/tests/communication/verify_mpi_lifecycle_failure.cmake b/parallel/parallel_src/tests/communication/verify_mpi_lifecycle_failure.cmake new file mode 100644 index 00000000..d2ab262e --- /dev/null +++ b/parallel/parallel_src/tests/communication/verify_mpi_lifecycle_failure.cmake @@ -0,0 +1,44 @@ +if( + NOT DEFINED PROBE + OR NOT DEFINED MODE + OR NOT DEFINED EXPECTED_QUERY + OR NOT DEFINED EXPECTED_ERROR_CODE +) + message( + FATAL_ERROR + "PROBE, MODE, EXPECTED_QUERY, and EXPECTED_ERROR_CODE are required" + ) +endif() + +execute_process( + COMMAND "${PROBE}" "${MODE}" + RESULT_VARIABLE probe_result + OUTPUT_VARIABLE probe_stdout + ERROR_VARIABLE probe_stderr + TIMEOUT 5 +) + +if(NOT "${probe_result}" STREQUAL "86") + message( + FATAL_ERROR + "lifecycle probe did not observe std::abort (result=${probe_result})\nstdout:\n${probe_stdout}\nstderr:\n${probe_stderr}" + ) +endif() + +set(probe_output "${probe_stdout}\n${probe_stderr}") +if(probe_output MATCHES "forbidden MPI call") + message(FATAL_ERROR "${probe_output}") +endif() +if( + NOT probe_output + MATCHES + "MPI lifecycle query failure: ${EXPECTED_QUERY} returned raw error ${EXPECTED_ERROR_CODE}([^0-9]|$)" +) + message( + FATAL_ERROR + "missing exact raw lifecycle diagnostic for ${EXPECTED_QUERY} code ${EXPECTED_ERROR_CODE}\n${probe_output}" + ) +endif() +if(NOT probe_output MATCHES "observed SIGABRT from lifecycle-query failure") + message(FATAL_ERROR "missing SIGABRT observation\n${probe_output}") +endif() diff --git a/parallel/parallel_src/tests/communication/verify_mpi_neighborhood_failure.cmake b/parallel/parallel_src/tests/communication/verify_mpi_neighborhood_failure.cmake new file mode 100644 index 00000000..51748ff7 --- /dev/null +++ b/parallel/parallel_src/tests/communication/verify_mpi_neighborhood_failure.cmake @@ -0,0 +1,50 @@ +if( + NOT DEFINED MPIEXEC_EXECUTABLE + OR NOT DEFINED MPIEXEC_NUMPROC_FLAG + OR NOT DEFINED PROBE + OR NOT DEFINED MODE + OR NOT DEFINED EXPECTED_CONTEXT + OR NOT DEFINED EXPECTED_AFFECTED +) + message( + FATAL_ERROR + "MPI launcher, PROBE, MODE, EXPECTED_CONTEXT, and EXPECTED_AFFECTED are required" + ) +endif() + +execute_process( + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 1 + ${MPIEXEC_PREFLAGS} + "${PROBE}" "${MODE}" + ${MPIEXEC_POSTFLAGS} + RESULT_VARIABLE probe_result + OUTPUT_VARIABLE probe_stdout + ERROR_VARIABLE probe_stderr + TIMEOUT 5 +) + +if("${probe_result}" STREQUAL "0") + message( + FATAL_ERROR + "neighborhood failure probe returned success unexpectedly\nstdout:\n${probe_stdout}\nstderr:\n${probe_stderr}" + ) +endif() + +set(probe_output "${probe_stdout}\n${probe_stderr}") +if(NOT probe_output MATCHES "MPI backend failure: ${EXPECTED_CONTEXT}") + message( + FATAL_ERROR + "missing fail-fast diagnostic for ${EXPECTED_CONTEXT}\n${probe_output}" + ) +endif() +if( + NOT probe_output + MATCHES + "observed MPI_Abort from neighborhood-${MODE} failure on ${EXPECTED_AFFECTED} communicator" +) + message( + FATAL_ERROR + "missing affected-communicator abort marker for ${MODE}\n${probe_output}" + ) +endif() diff --git a/parallel/parallel_src/tests/communication/verify_mpi_semantic_exit_policy.cmake b/parallel/parallel_src/tests/communication/verify_mpi_semantic_exit_policy.cmake new file mode 100644 index 00000000..f31db93d --- /dev/null +++ b/parallel/parallel_src/tests/communication/verify_mpi_semantic_exit_policy.cmake @@ -0,0 +1,188 @@ +cmake_minimum_required(VERSION 4.0) + +if(NOT DEFINED COMMUNICATION_ROOT) + message(FATAL_ERROR "COMMUNICATION_ROOT is required") +endif() + +set( + region_files + mpi_collectives.h + mpi_collectives.h + mpi_collectives.h + mpi_neighbors.cpp + mpi_neighbors.h + mpi_async_neighbors.h +) +set( + region_names + validate-collectively + agree-collectively + dense-all-to-all + distributed-graph-rank-domain + sync-neighbor + async-direct +) + +function(count_literal text literal output_variable) + string(LENGTH "${literal}" literal_length) + if(literal_length EQUAL 0) + message(FATAL_ERROR "cannot count an empty literal") + endif() + + set(remaining "${text}") + set(count 0) + while(TRUE) + string(FIND "${remaining}" "${literal}" literal_index) + if(literal_index EQUAL -1) + break() + endif() + math(EXPR count "${count} + 1") + math(EXPR next_index "${literal_index} + ${literal_length}") + string(SUBSTRING "${remaining}" ${next_index} -1 remaining) + endwhile() + set(${output_variable} ${count} PARENT_SCOPE) +endfunction() + +set(policy_violations) +list(LENGTH region_files file_count) +list(LENGTH region_names region_count) +if(NOT file_count EQUAL region_count) + message( + FATAL_ERROR + "semantic exit audit configuration mismatch: ${file_count} files, ${region_count} names" + ) +endif() +math(EXPR last_region "${region_count} - 1") +foreach(region_index RANGE 0 ${last_region}) + list(GET region_files ${region_index} region_file) + list(GET region_names ${region_index} region_name) + file(READ "${COMMUNICATION_ROOT}/${region_file}" source) + + set(begin_marker "// KAHIP_SEMANTIC_EXIT_BEGIN(${region_name})") + set(end_marker "// KAHIP_SEMANTIC_EXIT_END(${region_name})") + count_literal("${source}" "${begin_marker}" begin_count) + count_literal("${source}" "${end_marker}" end_count) + if(NOT begin_count EQUAL 1 OR NOT end_count EQUAL 1) + list( + APPEND + policy_violations + "${region_name}: expected one ordered marker pair, found ${begin_count}/${end_count}" + ) + continue() + endif() + + string(FIND "${source}" "${begin_marker}" begin_index) + string(FIND "${source}" "${end_marker}" end_index) + string(LENGTH "${begin_marker}" begin_length) + math(EXPR content_begin "${begin_index} + ${begin_length}") + if(end_index LESS content_begin) + list(APPEND policy_violations "${region_name}: marker order is reversed") + continue() + endif() + math(EXPR content_length "${end_index} - ${content_begin}") + string(SUBSTRING "${source}" ${content_begin} ${content_length} region) + string(REGEX REPLACE "[ \t\r\n]" "" normalized_region "${region}") + + string(FIND "${normalized_region}" "throwmpi_error" direct_throw) + string( + FIND + "${normalized_region}" + "throwparhip::mpi::mpi_error" + qualified_direct_throw + ) + if(NOT direct_throw EQUAL -1 OR NOT qualified_direct_throw EQUAL -1) + list(APPEND policy_violations "${region_name}: direct throw mpi_error") + endif() + + count_literal( + "${normalized_region}" + "throw_collectively_agreed_semantic_error(" + helper_count + ) + if(NOT helper_count EQUAL 1) + list( + APPEND + policy_violations + "${region_name}: expected one central semantic helper call, found ${helper_count}" + ) + endif() +endforeach() + +# These graph algorithms enter semantic exits only after every rank has made +# the same decision. Keep their error construction behind the same +# allocation-safe barrier as the communication adapter: constructing or +# copying mpi_error must never let one rank unwind while its peers continue. +set( + graph_exit_files + ghost_exchange_plan.cpp + ../data_structure/parallel_graph_access.cpp + ../distributed_partitioning/distributed_partitioner.cpp + ../parallel_contraction_projection/parallel_contraction.cpp + ../parallel_contraction_projection/parallel_block_down_propagation.cpp +) +set( + graph_exit_names + ghost-exchange-plan + parallel-graph-access + distributed-partitioner + parallel-contraction + parallel-block-down +) +set(graph_exit_helper_counts 2 2 2 3 3) + +list(LENGTH graph_exit_files graph_exit_file_count) +math(EXPR last_graph_exit "${graph_exit_file_count} - 1") +foreach(graph_exit_index RANGE 0 ${last_graph_exit}) + list(GET graph_exit_files ${graph_exit_index} graph_exit_file) + list(GET graph_exit_names ${graph_exit_index} graph_exit_name) + list(GET graph_exit_helper_counts ${graph_exit_index} expected_helper_count) + file(READ "${COMMUNICATION_ROOT}/${graph_exit_file}" source) + + string(REGEX REPLACE "[ \t\r\n]" "" normalized_source "${source}") + string(FIND "${normalized_source}" "throwmpi::mpi_error" direct_throw) + string( + FIND + "${normalized_source}" + "throwparhip::mpi::mpi_error" + qualified_direct_throw + ) + string(FIND "${normalized_source}" "make_exception_ptr(" exception_copy) + if( + NOT direct_throw EQUAL -1 + OR NOT qualified_direct_throw EQUAL -1 + OR NOT exception_copy EQUAL -1 + ) + list( + APPEND + policy_violations + "${graph_exit_name}: semantic error construction bypasses central helper" + ) + endif() + + count_literal( + "${normalized_source}" + "throw_collectively_agreed_semantic_error(" + direct_helper_count + ) + count_literal( + "${normalized_source}" + "throw_collectively_agreed_semantic_error_from(" + factory_helper_count + ) + math(EXPR helper_count "${direct_helper_count} + ${factory_helper_count}") + if(NOT helper_count EQUAL expected_helper_count) + list( + APPEND + policy_violations + "${graph_exit_name}: expected ${expected_helper_count} central semantic helper calls, found ${helper_count}" + ) + endif() +endforeach() + +if(policy_violations) + string(JOIN "\n - " formatted_violations ${policy_violations}) + message( + FATAL_ERROR + "collectively agreed semantic exit policy violations:\n - ${formatted_violations}" + ) +endif() diff --git a/parallel/parallel_src/tests/communication/verify_mpi_trace_oracle.cmake b/parallel/parallel_src/tests/communication/verify_mpi_trace_oracle.cmake new file mode 100644 index 00000000..2520df82 --- /dev/null +++ b/parallel/parallel_src/tests/communication/verify_mpi_trace_oracle.cmake @@ -0,0 +1,366 @@ +cmake_minimum_required(VERSION 4.0) + +foreach(required IN ITEMS + MANIFEST_PATH + PATCH_PATH + REPOSITORY_ROOT + GRAPH_PATH + PARTITION_PATH + TRACE_BASE + TRACE_RUN_ID + EXPECTED_RANKS + EXPECTED_K + EXPECTED_PRECONFIGURATION + EXPECTED_SEED) + if(NOT DEFINED ${required}) + message(FATAL_ERROR "${required} is required") + endif() +endforeach() + +foreach(required_file IN ITEMS MANIFEST_PATH PATCH_PATH GRAPH_PATH PARTITION_PATH) + if(NOT EXISTS "${${required_file}}") + message(FATAL_ERROR "${required_file} does not exist: ${${required_file}}") + endif() +endforeach() + +function(manifest_variable_name key output) + string(REPLACE "." "_" variable_name "${key}") + string(REPLACE "-" "_" variable_name "${variable_name}") + set(${output} "oracle_${variable_name}" PARENT_SCOPE) +endfunction() + +function(require_unsigned name value) + if(NOT value MATCHES "^[0-9]+$") + message(FATAL_ERROR "${name} must be an unsigned integer, got '${value}'") + endif() +endfunction() + +function(require_lower_hex name value length) + string(LENGTH "${value}" actual_length) + if(NOT actual_length EQUAL length OR NOT value MATCHES "^[0-9a-f]+$") + message(FATAL_ERROR + "${name} must be ${length} lowercase hexadecimal characters" + ) + endif() +endfunction() + +require_unsigned("EXPECTED_RANKS" "${EXPECTED_RANKS}") +require_unsigned("EXPECTED_K" "${EXPECTED_K}") +require_unsigned("EXPECTED_SEED" "${EXPECTED_SEED}") +if(EXPECTED_RANKS LESS 1) + message(FATAL_ERROR "EXPECTED_RANKS must be positive") +endif() + +set(fixed_manifest_keys + upstream_revision + instrumentation_patch + instrumentation_patch_sha256 + tuple.graph + tuple.ranks + tuple.k + tuple.preconfiguration + tuple.seed + partition_sha256 + trace_format + canonical_rank_aggregate_records + canonical_rank_aggregate_sha256 +) +set(allowed_stage_keys + stage.graph-distribution-node + stage.graph-distribution-edge + stage.contraction-label + stage.quotient-node-weight + stage.quotient-edge + stage.projection-request + stage.projection-reply + stage.ghost-update + stage.block-propagation + stage.final-partition +) +set(dynamic_rank_keys "") +math(EXPR last_expected_rank "${EXPECTED_RANKS} - 1") +foreach(rank RANGE 0 ${last_expected_rank}) + list(APPEND dynamic_rank_keys + "upstream_rank${rank}_sha256" + "candidate_rank${rank}_sha256" + ) +endforeach() +set(allowed_manifest_keys + ${fixed_manifest_keys} + ${dynamic_rank_keys} + ${allowed_stage_keys} +) + +file(STRINGS "${MANIFEST_PATH}" manifest_lines ENCODING UTF-8) +set(seen_manifest_keys "") +set(manifest_stage_keys "") +set(saw_manifest_title FALSE) +foreach(raw_line IN LISTS manifest_lines) + string(STRIP "${raw_line}" line) + if(line STREQUAL "" OR line MATCHES "^#") + continue() + endif() + if(NOT line MATCHES "=") + if(saw_manifest_title OR seen_manifest_keys OR + NOT line MATCHES "^KaHIP .+ oracle .+$") + message(FATAL_ERROR "malformed oracle manifest line: '${line}'") + endif() + set(saw_manifest_title TRUE) + continue() + endif() + if(NOT line MATCHES "^([A-Za-z0-9_.-]+)=(.+)$") + message(FATAL_ERROR "malformed oracle manifest entry: '${line}'") + endif() + set(key "${CMAKE_MATCH_1}") + set(value "${CMAKE_MATCH_2}") + list(FIND allowed_manifest_keys "${key}" allowed_index) + if(allowed_index EQUAL -1) + message(FATAL_ERROR "unknown oracle manifest key '${key}'") + endif() + list(FIND seen_manifest_keys "${key}" seen_index) + if(NOT seen_index EQUAL -1) + message(FATAL_ERROR "duplicate manifest key '${key}'") + endif() + list(APPEND seen_manifest_keys "${key}") + if(key MATCHES "^stage\\.") + list(APPEND manifest_stage_keys "${key}") + endif() + manifest_variable_name("${key}" variable_name) + set("${variable_name}" "${value}") +endforeach() + +if(NOT saw_manifest_title) + message(FATAL_ERROR "oracle manifest is missing its provenance title") +endif() +foreach(required_key IN LISTS fixed_manifest_keys dynamic_rank_keys) + list(FIND seen_manifest_keys "${required_key}" required_index) + if(required_index EQUAL -1) + message(FATAL_ERROR "oracle manifest is missing '${required_key}'") + endif() +endforeach() +if(NOT manifest_stage_keys) + message(FATAL_ERROR "oracle manifest contains no stage counts") +endif() + +require_lower_hex("upstream revision" "${oracle_upstream_revision}" 40) +require_lower_hex( + "instrumentation patch SHA-256" + "${oracle_instrumentation_patch_sha256}" + 64 +) +require_lower_hex("partition SHA-256" "${oracle_partition_sha256}" 64) +require_lower_hex( + "canonical trace aggregate SHA-256" + "${oracle_canonical_rank_aggregate_sha256}" + 64 +) +require_unsigned( + "canonical trace aggregate record count" + "${oracle_canonical_rank_aggregate_records}" +) +foreach(rank RANGE 0 ${last_expected_rank}) + foreach(kind IN ITEMS upstream candidate) + manifest_variable_name("${kind}_rank${rank}_sha256" hash_variable) + require_lower_hex( + "${kind} rank ${rank} trace SHA-256" + "${${hash_variable}}" + 64 + ) + endforeach() +endforeach() +foreach(stage_key IN LISTS manifest_stage_keys) + manifest_variable_name("${stage_key}" stage_count_variable) + require_unsigned( + "${stage_key} count" "${${stage_count_variable}}" + ) +endforeach() + +if(NOT oracle_tuple_ranks STREQUAL "${EXPECTED_RANKS}") + message(FATAL_ERROR + "oracle tuple ranks are ${oracle_tuple_ranks}, expected ${EXPECTED_RANKS}" + ) +endif() +if(NOT oracle_tuple_k STREQUAL "${EXPECTED_K}") + message(FATAL_ERROR + "oracle tuple k is ${oracle_tuple_k}, expected ${EXPECTED_K}" + ) +endif() +if(NOT oracle_tuple_preconfiguration STREQUAL + "${EXPECTED_PRECONFIGURATION}") + message(FATAL_ERROR + "oracle tuple preconfiguration is '${oracle_tuple_preconfiguration}', expected '${EXPECTED_PRECONFIGURATION}'" + ) +endif() +if(NOT oracle_tuple_seed STREQUAL "${EXPECTED_SEED}") + message(FATAL_ERROR + "oracle tuple seed is ${oracle_tuple_seed}, expected ${EXPECTED_SEED}" + ) +endif() +if(NOT oracle_trace_format STREQUAL "kahip-mpi-trace-v3") + message(FATAL_ERROR + "unsupported oracle trace format '${oracle_trace_format}'" + ) +endif() + +get_filename_component(patch_name "${PATCH_PATH}" NAME) +if(NOT patch_name STREQUAL oracle_instrumentation_patch) + message(FATAL_ERROR + "instrumentation patch is '${patch_name}', manifest names '${oracle_instrumentation_patch}'" + ) +endif() +file(SHA256 "${PATCH_PATH}" actual_patch_sha256) +if(NOT actual_patch_sha256 STREQUAL oracle_instrumentation_patch_sha256) + message(FATAL_ERROR + "instrumentation patch SHA-256 is ${actual_patch_sha256}, expected ${oracle_instrumentation_patch_sha256}" + ) +endif() + +file(REAL_PATH "${REPOSITORY_ROOT}/${oracle_tuple_graph}" oracle_graph_path) +file(REAL_PATH "${GRAPH_PATH}" actual_graph_path) +if(NOT actual_graph_path STREQUAL oracle_graph_path) + message(FATAL_ERROR + "oracle graph is '${actual_graph_path}', expected '${oracle_graph_path}'" + ) +endif() + +file(SHA256 "${PARTITION_PATH}" actual_partition_sha256) +if(NOT actual_partition_sha256 STREQUAL oracle_partition_sha256) + message(FATAL_ERROR + "partition SHA-256 is ${actual_partition_sha256}, expected ${oracle_partition_sha256}" + ) +endif() + +set(expected_header + "${oracle_trace_format} upstream=${oracle_upstream_revision}" +) +set(common_trace_stem "") +set(rank_record_files "") +set(actual_total_records 0) +foreach(stage_key IN LISTS manifest_stage_keys) + manifest_variable_name("${stage_key}" stage_variable) + set("actual_${stage_variable}" 0) +endforeach() + +foreach(rank RANGE 0 ${last_expected_rank}) + file(GLOB rank_trace_files + "${TRACE_BASE}.run-${TRACE_RUN_ID}-*.rank${rank}.trace" + ) + list(LENGTH rank_trace_files rank_trace_file_count) + if(NOT rank_trace_file_count EQUAL 1) + message(FATAL_ERROR + "expected one trace file for rank ${rank}, found ${rank_trace_file_count}: ${rank_trace_files}" + ) + endif() + list(GET rank_trace_files 0 trace_file) + string( + REGEX REPLACE "\\.rank[0-9]+\\.trace$" "" trace_stem "${trace_file}" + ) + if(common_trace_stem STREQUAL "") + set(common_trace_stem "${trace_stem}") + elseif(NOT trace_stem STREQUAL common_trace_stem) + message(FATAL_ERROR + "trace ranks do not share one run ID: ${common_trace_stem};${trace_stem}" + ) + endif() + + file(SHA256 "${trace_file}" actual_rank_sha256) + manifest_variable_name("candidate_rank${rank}_sha256" rank_hash_variable) + if(NOT actual_rank_sha256 STREQUAL "${${rank_hash_variable}}") + message(FATAL_ERROR + "rank ${rank} trace SHA-256 is ${actual_rank_sha256}, expected ${${rank_hash_variable}}" + ) + endif() + + file(READ "${trace_file}" trace_contents) + string(FIND "${trace_contents}" "\n" header_end) + if(header_end LESS 0) + message(FATAL_ERROR "rank ${rank} trace has no complete header") + endif() + string(SUBSTRING "${trace_contents}" 0 ${header_end} rank_header) + if(NOT rank_header STREQUAL expected_header) + message(FATAL_ERROR + "rank ${rank} trace header is '${rank_header}', expected '${expected_header}'" + ) + endif() + math(EXPR record_text_begin "${header_end} + 1") + string(SUBSTRING "${trace_contents}" ${record_text_begin} -1 record_text) + set(rank_record_file "${TRACE_BASE}.rank${rank}.records") + file(WRITE "${rank_record_file}" "${record_text}") + list(APPEND rank_record_files "${rank_record_file}") + + file(STRINGS "${trace_file}" rank_lines ENCODING UTF-8) + list(POP_FRONT rank_lines parsed_header) + if(NOT parsed_header STREQUAL expected_header) + message(FATAL_ERROR "rank ${rank} trace header changed while parsing") + endif() + foreach(record IN LISTS rank_lines) + if(NOT record MATCHES + "^([a-z][a-z-]*) cycle=[0-9]+ level=[0-9]+ epoch=[a-z-]+ iteration=[0-9]+ round=[0-9]+ global=[0-9]+ owner=(-|[0-9]+) requester=(-|[0-9]+) receiver=(-|[0-9]+) key=[^ ]+( .+)?$") + message(FATAL_ERROR + "rank ${rank} trace contains a malformed record: '${record}'" + ) + endif() + set(stage_key "stage.${CMAKE_MATCH_1}") + list(FIND manifest_stage_keys "${stage_key}" stage_index) + if(stage_index EQUAL -1) + message(FATAL_ERROR + "rank ${rank} trace contains unmanifested stage '${stage_key}'" + ) + endif() + manifest_variable_name("${stage_key}" stage_variable) + math(EXPR actual_total_records "${actual_total_records} + 1") + math( + EXPR "actual_${stage_variable}" + "${actual_${stage_variable}} + 1" + ) + endforeach() +endforeach() + +file(GLOB all_trace_files "${TRACE_BASE}.run-${TRACE_RUN_ID}-*.rank*.trace") +list(LENGTH all_trace_files all_trace_file_count) +if(NOT all_trace_file_count EQUAL EXPECTED_RANKS) + message(FATAL_ERROR + "trace run produced ${all_trace_file_count} rank files, expected ${EXPECTED_RANKS}" + ) +endif() + +if(NOT actual_total_records EQUAL oracle_canonical_rank_aggregate_records) + message(FATAL_ERROR + "canonical trace aggregate contains ${actual_total_records} records, expected ${oracle_canonical_rank_aggregate_records}" + ) +endif() +foreach(stage_key IN LISTS manifest_stage_keys) + manifest_variable_name("${stage_key}" stage_variable) + if(NOT actual_${stage_variable} EQUAL ${${stage_variable}}) + message(FATAL_ERROR + "${stage_key} count is ${actual_${stage_variable}}, expected ${${stage_variable}}" + ) + endif() +endforeach() + +find_program(trace_sort_executable NAMES sort REQUIRED) +set(aggregate_path "${TRACE_BASE}.canonical.records") +execute_process( + COMMAND + "${CMAKE_COMMAND}" -E env "LC_ALL=C" "${trace_sort_executable}" + ${rank_record_files} + RESULT_VARIABLE sort_result + OUTPUT_FILE "${aggregate_path}" + ERROR_VARIABLE sort_error +) +if(NOT sort_result EQUAL 0) + message(FATAL_ERROR + "canonical trace sort failed (${sort_result}): ${sort_error}" + ) +endif() +file(SHA256 "${aggregate_path}" actual_aggregate_sha256) +if(NOT actual_aggregate_sha256 STREQUAL + oracle_canonical_rank_aggregate_sha256) + message(FATAL_ERROR + "canonical trace aggregate SHA-256 is ${actual_aggregate_sha256}, expected ${oracle_canonical_rank_aggregate_sha256}" + ) +endif() + +message(STATUS + "exact MPI oracle verified: partition=${actual_partition_sha256} records=${actual_total_records} trace=${actual_aggregate_sha256}" +) diff --git a/parallel/parallel_src/tests/communication/verify_pmpi_callback_safety.cmake b/parallel/parallel_src/tests/communication/verify_pmpi_callback_safety.cmake new file mode 100644 index 00000000..c214c3c8 --- /dev/null +++ b/parallel/parallel_src/tests/communication/verify_pmpi_callback_safety.cmake @@ -0,0 +1,415 @@ +cmake_minimum_required(VERSION 4.0) + +if(NOT DEFINED SOURCE_FILE OR SOURCE_FILE STREQUAL "") + message(FATAL_ERROR "SOURCE_FILE is required") +endif() +if(NOT EXISTS "${SOURCE_FILE}") + message(FATAL_ERROR "PMPI callback source does not exist: ${SOURCE_FILE}") +endif() +if(NOT DEFINED PROFILE OR PROFILE STREQUAL "") + set(PROFILE projection) +endif() + +file(READ "${SOURCE_FILE}" callback_source) +set(begin_marker "// KAHIP_PMPI_CALLBACK_REGION_BEGIN") +set(end_marker "// KAHIP_PMPI_CALLBACK_REGION_END") + +string(REGEX MATCHALL "${begin_marker}" begin_matches "${callback_source}") +string(REGEX MATCHALL "${end_marker}" end_matches "${callback_source}") +list(LENGTH begin_matches begin_count) +list(LENGTH end_matches end_count) +if(NOT begin_count EQUAL 1 OR NOT end_count EQUAL 1) + message( + FATAL_ERROR + "${PROFILE} callback audit requires exactly one ordered marker pair; found begin=${begin_count}, end=${end_count}" + ) +endif() + +string(FIND "${callback_source}" "${begin_marker}" begin_index) +string(FIND "${callback_source}" "${end_marker}" end_index) +if(begin_index GREATER_EQUAL end_index) + message(FATAL_ERROR "${PROFILE} callback audit markers are reversed") +endif() +string(LENGTH "${begin_marker}" begin_marker_length) +math(EXPR content_begin "${begin_index} + ${begin_marker_length}") +math(EXPR content_length "${end_index} - ${content_begin}") +string( + SUBSTRING "${callback_source}" ${content_begin} ${content_length} + callback_region +) + +set(audit_errors "") +function(reject_callback_pattern label pattern) + string(REGEX MATCHALL "${pattern}" matches "${callback_region}") + if(matches) + list(LENGTH matches match_count) + string(APPEND audit_errors "\n- ${label}: ${match_count} match(es)") + set(audit_errors "${audit_errors}" PARENT_SCOPE) + endif() +endfunction() + +reject_callback_pattern( + "Catch assertion macro reachable from an extern-C MPI wrapper" + [=[(^|[^A-Za-z0-9_])((STATIC_)?(REQUIRE|CHECK)|(UNSCOPED_)?INFO|FAIL|CAPTURE|WARN|SUCCEED|DYNAMIC_SECTION|SECTION)[A-Z0-9_]*[ \t\r\n]*\(]=] +) +reject_callback_pattern( + "C++ throw expression reachable from an extern-C MPI wrapper" + [=[(^|[^A-Za-z0-9_])throw([^A-Za-z0-9_]|$)]=] +) +reject_callback_pattern( + "dynamic standard container, callable, or string in callback state/helper code" + [=[std::(vector|deque|list|map|set|unordered_map|unordered_set|function)[ \t\r\n]*<|std::(basic_string|string)([^A-Za-z0-9_]|$)]=] +) +reject_callback_pattern( + "allocation-capable operation in callback state/helper code" + [=[(^|[^A-Za-z0-9_])(new|malloc|calloc|realloc|aligned_alloc)([^A-Za-z0-9_]|$)|std::(allocator|allocator_traits)[ \t\r\n]*<|[.:](allocate|allocate_at_least|push_back|emplace_back|reserve|resize|insert|assign|append|replace)[ \t\r\n]*\(]=] +) +reject_callback_pattern( + "smart-pointer allocation in callback state/helper code" + [=[std::make_unique[ \t\r\n]*<|std::make_shared[ \t\r\n]*<|std::allocate_shared[ \t\r\n]*<]=] +) +reject_callback_pattern( + "allocation-capable formatting or diagnostics in callback code" + [=[(fmt|spdlog)::|kahip::diagnostics::critical[ \t\r\n]*\(|std::(format|vformat|to_string|print|println)[ \t\r\n]*\(|std::((basic_)?(ostringstream|stringstream|istringstream|fstream|ofstream|ifstream)|ostream|iostream|cout|cerr|clog)([^A-Za-z0-9_]|$)]=] +) + +if(PROFILE STREQUAL "projection") + set( + required_noexcept_patterns + [=[auto[ \t\r\n]+dense_payload_collective_calls\(\)[ \t\r\n]+noexcept[ \t\r\n]+->[ \t\r\n]+int]=] + [=[void[ \t\r\n]+mutate_received_payload\([^)]*\)[ \t\r\n]+noexcept]=] + ) + set( + required_compile_time_check_patterns + [=[static_assert *\( *noexcept *\( *protocol_probe::dense_payload_collective_calls *\( *\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *protocol_probe::mutate_received_payload *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *protocol_probe::mutate_received_payload *\([^)]*\) *\) *\) *[;]]=] + ) +elseif(PROFILE STREQUAL "ghost-label") + set( + required_noexcept_patterns + [=[void[ \t\r\n]+corrupt_neighbor_payload\([^)]*\)[ \t\r\n]+noexcept]=] + [=[void[ \t\r\n]+corrupt_legacy_payload\([^)]*\)[ \t\r\n]+noexcept]=] + [=[void[ \t\r\n]+observe_immediate_payload\([^)]*\)[ \t\r\n]+noexcept]=] + ) + set( + required_compile_time_check_patterns + [=[static_assert *\( *noexcept *\( *ghost_label_probe::corrupt_neighbor_payload *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *ghost_label_probe::corrupt_neighbor_payload *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *ghost_label_probe::corrupt_legacy_payload *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *ghost_label_probe::observe_immediate_payload *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *ghost_label_probe::observe_immediate_payload *\([^)]*\) *\) *\) *[;]]=] + ) +elseif(PROFILE STREQUAL "ghost-label-failure") + set( + required_noexcept_patterns + [=[void[ \t\r\n]+track_incremental_payload\([^)]*\)[ \t\r\n]+noexcept]=] + [=[void[ \t\r\n]+corrupt_completed_incremental_payload\(\)[ \t\r\n]+noexcept]=] + [=[auto[ \t\r\n]+expected_abort_state\(\)[ \t\r\n]+noexcept[ \t\r\n]+->[ \t\r\n]+bool]=] + ) + set( + required_compile_time_check_patterns + [=[static_assert *\( *noexcept *\( *ghost_failure_probe::track_incremental_payload *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *ghost_failure_probe::track_incremental_payload *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *ghost_failure_probe::corrupt_completed_incremental_payload *\( *\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *ghost_failure_probe::expected_abort_state *\( *\) *\) *\) *[;]]=] + ) +elseif(PROFILE STREQUAL "population-size") + set( + required_noexcept_patterns + [=[void[ \t\r\n]+reset\([^)]*\)[ \t\r\n]+noexcept]=] + [=[void[ \t\r\n]+observe_broadcast\([^)]*\)[ \t\r\n]+noexcept]=] + ) + set( + required_compile_time_check_patterns + [=[static_assert *\( *noexcept *\( *population_size_probe::reset *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *population_size_probe::observe_broadcast *\([^)]*\) *\) *\) *[;]]=] + ) +elseif(PROFILE STREQUAL "population-size-failure") + set( + required_noexcept_patterns + [=[void[ \t\r\n]+write_text\([^)]*\)[ \t\r\n]+noexcept]=] + [=[void[ \t\r\n]+observed_abort\([^)]*\)[ \t\r\n]+noexcept]=] + ) + set( + required_compile_time_check_patterns + [=[static_assert *\( *noexcept *\( *population_failure_probe::write_text *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *population_failure_probe::observed_abort *\([^)]*\) *\) *\) *[;]]=] + ) +elseif(PROFILE STREQUAL "evolutionary") + set( + required_noexcept_patterns + [=[void[ \t\r\n]+reset\([^)]*\)[ \t\r\n]+noexcept]=] + [=[void[ \t\r\n]+record\([^)]*\)[ \t\r\n]+noexcept]=] + ) + set( + required_compile_time_check_patterns + [=[static_assert *\( *noexcept *\( *evolutionary_probe::reset *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *evolutionary_probe::record *\([^)]*\) *\) *\) *[;]]=] + ) +elseif(PROFILE STREQUAL "evolutionary-failure") + set( + required_noexcept_patterns + [=[void[ \t\r\n]+write_text\([^)]*\)[ \t\r\n]+noexcept]=] + [=[void[ \t\r\n]+observed_abort\([^)]*\)[ \t\r\n]+noexcept]=] + [=[auto[ \t\r\n]+objective_datatype\([^)]*\)[ \t\r\n]+noexcept[ \t\r\n]+->[ \t\r\n]+MPI_Datatype]=] + [=[auto[ \t\r\n]+valid_allreduce\([^)]*\)[ \t\r\n]+noexcept[ \t\r\n]+->[ \t\r\n]+bool]=] + [=[auto[ \t\r\n]+expected_abort_state\([^)]*\)[ \t\r\n]+noexcept[ \t\r\n]+->[ \t\r\n]+bool]=] + ) + set( + required_compile_time_check_patterns + [=[static_assert *\( *noexcept *\( *evolutionary_failure_probe::write_text *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *evolutionary_failure_probe::observed_abort *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *evolutionary_failure_probe::objective_datatype *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *evolutionary_failure_probe::valid_allreduce *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *evolutionary_failure_probe::expected_abort_state *\([^)]*\) *\) *\) *[;]]=] + ) +elseif(PROFILE STREQUAL "fixed-broadcast") + set( + required_noexcept_patterns + [=[void[ \t\r\n]+reset\([^)]*\)[ \t\r\n]+noexcept]=] + [=[void[ \t\r\n]+record\([^)]*\)[ \t\r\n]+noexcept]=] + ) + set( + required_compile_time_check_patterns + [=[static_assert *\( *noexcept *\( *fixed_broadcast_probe::reset *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *fixed_broadcast_probe::record *\([^)]*\) *\) *\) *[;]]=] + ) +elseif(PROFILE STREQUAL "fixed-broadcast-failure") + set( + required_noexcept_patterns + [=[void[ \t\r\n]+write_text\([^)]*\)[ \t\r\n]+noexcept]=] + [=[auto[ \t\r\n]+valid_broadcast\([^)]*\)[ \t\r\n]+noexcept[ \t\r\n]+->[ \t\r\n]+bool]=] + [=[auto[ \t\r\n]+expected_abort_state\([^)]*\)[ \t\r\n]+noexcept[ \t\r\n]+->[ \t\r\n]+bool]=] + [=[void[ \t\r\n]+observed_abort\([^)]*\)[ \t\r\n]+noexcept]=] + ) + set( + required_compile_time_check_patterns + [=[static_assert *\( *noexcept *\( *fixed_broadcast_failure_probe::write_text *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *fixed_broadcast_failure_probe::valid_broadcast *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *fixed_broadcast_failure_probe::expected_abort_state *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *fixed_broadcast_failure_probe::observed_abort *\([^)]*\) *\) *\) *[;]]=] + ) +elseif(PROFILE STREQUAL "vertex-cut") + set( + required_noexcept_patterns + [=[void[ \t\r\n]+reset\([^)]*\)[ \t\r\n]+noexcept]=] + [=[void[ \t\r\n]+record\([^)]*\)[ \t\r\n]+noexcept]=] + ) + set( + required_compile_time_check_patterns + [=[static_assert *\( *noexcept *\( *vertex_cut_probe::reset *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *vertex_cut_probe::record *\([^)]*\) *\) *\) *[;]]=] + ) +elseif(PROFILE STREQUAL "vertex-cut-failure") + set( + required_noexcept_patterns + [=[void[ \t\r\n]+write_text\([^)]*\)[ \t\r\n]+noexcept]=] + [=[auto[ \t\r\n]+valid_all_reduce\([^)]*\)[ \t\r\n]+noexcept[ \t\r\n]+->[ \t\r\n]+bool]=] + [=[auto[ \t\r\n]+expected_abort_state\([^)]*\)[ \t\r\n]+noexcept[ \t\r\n]+->[ \t\r\n]+bool]=] + [=[void[ \t\r\n]+observed_abort\([^)]*\)[ \t\r\n]+noexcept]=] + ) + set( + required_compile_time_check_patterns + [=[static_assert *\( *noexcept *\( *vertex_cut_failure_probe::write_text *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *vertex_cut_failure_probe::valid_all_reduce *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *vertex_cut_failure_probe::expected_abort_state *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *vertex_cut_failure_probe::observed_abort *\([^)]*\) *\) *\) *[;]]=] + ) +elseif(PROFILE STREQUAL "quality-metrics") + set( + required_noexcept_patterns + [=[void[ \t\r\n]+reset\([^)]*\)[ \t\r\n]+noexcept]=] + [=[void[ \t\r\n]+record\([^)]*\)[ \t\r\n]+noexcept]=] + ) + set( + required_compile_time_check_patterns + [=[static_assert *\( *noexcept *\( *quality_metrics_probe::reset *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *quality_metrics_probe::record *\([^)]*\) *\) *\) *[;]]=] + ) +elseif(PROFILE STREQUAL "quality-metrics-failure") + set( + required_noexcept_patterns + [=[void[ \t\r\n]+write_text\([^)]*\)[ \t\r\n]+noexcept]=] + [=[auto[ \t\r\n]+valid_control\([^)]*\)[ \t\r\n]+noexcept[ \t\r\n]+->[ \t\r\n]+bool]=] + [=[auto[ \t\r\n]+valid_payload\([^)]*\)[ \t\r\n]+noexcept[ \t\r\n]+->[ \t\r\n]+bool]=] + [=[auto[ \t\r\n]+expected_abort_state\([^)]*\)[ \t\r\n]+noexcept[ \t\r\n]+->[ \t\r\n]+bool]=] + [=[void[ \t\r\n]+observed_abort\([^)]*\)[ \t\r\n]+noexcept]=] + ) + set( + required_compile_time_check_patterns + [=[static_assert *\( *noexcept *\( *quality_metrics_failure_probe::write_text *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *quality_metrics_failure_probe::valid_control *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *quality_metrics_failure_probe::valid_payload *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *quality_metrics_failure_probe::expected_abort_state *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *quality_metrics_failure_probe::observed_abort *\([^)]*\) *\) *\) *[;]]=] + ) +elseif(PROFILE STREQUAL "dspac-first-split") + set( + required_noexcept_patterns + [=[void[ \t\r\n]+reset\([^)]*\)[ \t\r\n]+noexcept]=] + [=[void[ \t\r\n]+record_legacy_payload\([^)]*\)[ \t\r\n]+noexcept]=] + ) + set( + required_compile_time_check_patterns + [=[static_assert *\( *noexcept *\( *dspac_first_split_probe::reset *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *dspac_first_split_probe::record_legacy_payload *\([^)]*\) *\) *\) *[;]]=] + ) +elseif(PROFILE STREQUAL "dspac-first-split-failure") + set( + required_noexcept_patterns + [=[void[ \t\r\n]+write_text\([^)]*\)[ \t\r\n]+noexcept]=] + [=[auto[ \t\r\n]+expected_abort_state\([^)]*\)[ \t\r\n]+noexcept[ \t\r\n]+->[ \t\r\n]+bool]=] + [=[void[ \t\r\n]+observed_abort\([^)]*\)[ \t\r\n]+noexcept]=] + ) + set( + required_compile_time_check_patterns + [=[static_assert *\( *noexcept *\( *dspac_first_split_failure_probe::write_text *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *dspac_first_split_failure_probe::expected_abort_state *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *dspac_first_split_failure_probe::observed_abort *\([^)]*\) *\) *\) *[;]]=] + ) +elseif(PROFILE STREQUAL "balance-refinement-failure") + set( + required_noexcept_patterns + [=[void[ \t\r\n]+write_text\([^)]*\)[ \t\r\n]+noexcept]=] + [=[auto[ \t\r\n]+expected_abort_state\([^)]*\)[ \t\r\n]+noexcept[ \t\r\n]+->[ \t\r\n]+bool]=] + [=[void[ \t\r\n]+write_abort_marker\([^)]*\)[ \t\r\n]+noexcept]=] + [=[void[ \t\r\n]+observed_abort\([^)]*\)[ \t\r\n]+noexcept]=] + ) + set( + required_compile_time_check_patterns + [=[static_assert *\( *noexcept *\( *balance_refinement_failure_probe::write_text *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *balance_refinement_failure_probe::expected_abort_state *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *balance_refinement_failure_probe::write_abort_marker *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *balance_refinement_failure_probe::observed_abort *\([^)]*\) *\) *\) *[;]]=] + ) +elseif(PROFILE STREQUAL "parhip-interface-failure") + set( + required_noexcept_patterns + [=[void[ \t\r\n]+write_text\([^)]*\)[ \t\r\n]+noexcept]=] + [=[auto[ \t\r\n]+expects_duplicate\([^)]*\)[ \t\r\n]+noexcept[ \t\r\n]+->[ \t\r\n]+bool]=] + [=[auto[ \t\r\n]+expected_abort_state\([^)]*\)[ \t\r\n]+noexcept[ \t\r\n]+->[ \t\r\n]+bool]=] + [=[void[ \t\r\n]+observed_abort\([^)]*\)[ \t\r\n]+noexcept]=] + ) + set( + required_compile_time_check_patterns + [=[static_assert *\( *noexcept *\( *parhip_interface_failure_probe::write_text *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *parhip_interface_failure_probe::expects_duplicate *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *parhip_interface_failure_probe::expected_abort_state *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *parhip_interface_failure_probe::observed_abort *\([^)]*\) *\) *\) *[;]]=] + ) +elseif(PROFILE STREQUAL "evolutionary-lifetime") + set( + required_noexcept_patterns + [=[auto[ \t\r\n]+checksum\([^)]*\)[ \t\r\n]+noexcept]=] + [=[void[ \t\r\n]+reset\([^)]*\)[ \t\r\n]+noexcept]=] + [=[void[ \t\r\n]+record_send\([^)]*\)[ \t\r\n]+noexcept]=] + [=[void[ \t\r\n]+observe_completion\([^)]*\)[ \t\r\n]+noexcept]=] + [=[void[ \t\r\n]+finalize_observation\([^)]*\)[ \t\r\n]+noexcept]=] + ) + set( + required_compile_time_check_patterns + [=[static_assert *\( *noexcept *\( *evolutionary_lifetime_probe::checksum *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *evolutionary_lifetime_probe::reset *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *evolutionary_lifetime_probe::record_send *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *evolutionary_lifetime_probe::observe_completion *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *evolutionary_lifetime_probe::finalize_observation *\([^)]*\) *\) *\) *[;]]=] + ) +elseif(PROFILE STREQUAL "evolutionary-lifetime-failure") + set( + required_noexcept_patterns + [=[void[ \t\r\n]+write_text\([^)]*\)[ \t\r\n]+noexcept]=] + [=[auto[ \t\r\n]+expected_abort_state\([^)]*\)[ \t\r\n]+noexcept[ \t\r\n]+->[ \t\r\n]+bool]=] + [=[void[ \t\r\n]+observed_abort\([^)]*\)[ \t\r\n]+noexcept]=] + ) + set( + required_compile_time_check_patterns + [=[static_assert *\( *noexcept *\( *evolutionary_failure_probe::write_text *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *evolutionary_failure_probe::expected_abort_state *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *evolutionary_failure_probe::observed_abort *\([^)]*\) *\) *\) *[;]]=] + ) +elseif(PROFILE STREQUAL "edge-balanced-graph-io-failure") + set( + required_noexcept_patterns + [=[void[ \t\r\n]+write_text\([^)]*\)[ \t\r\n]+noexcept]=] + [=[void[ \t\r\n]+observed_abort\([^)]*\)[ \t\r\n]+noexcept]=] + ) + set( + required_compile_time_check_patterns + [=[static_assert *\( *noexcept *\( *edge_balanced_failure_probe::write_text *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *edge_balanced_failure_probe::observed_abort *\([^)]*\) *\) *\) *[;]]=] + ) +elseif(PROFILE STREQUAL "distributed-partitioner-failure") + set( + required_noexcept_patterns + [=[void[ \t\r\n]+write_text\([^)]*\)[ \t\r\n]+noexcept]=] + [=[auto[ \t\r\n]+labels_are_untouched\([^)]*\)[ \t\r\n]+noexcept[ \t\r\n]+->[ \t\r\n]+bool]=] + [=[void[ \t\r\n]+observed_abort\([^)]*\)[ \t\r\n]+noexcept]=] + ) + set( + required_compile_time_check_patterns + [=[static_assert *\( *noexcept *\( *distributed_partitioner_failure_probe::write_text *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *distributed_partitioner_failure_probe::labels_are_untouched *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *distributed_partitioner_failure_probe::observed_abort *\([^)]*\) *\) *\) *[;]]=] + ) +elseif(PROFILE STREQUAL "kaffpae-runtime-failure") + set( + required_noexcept_patterns + [=[void[ \t\r\n]+write_text\([^)]*\)[ \t\r\n]+noexcept]=] + [=[operation_communicator_matches]=] + ) + set( + required_compile_time_check_patterns + [=[static_assert *\( *noexcept *\( *write_text *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *operation_communicator_matches *\([^)]*\) *\) *\) *[;]]=] + ) +elseif(PROFILE STREQUAL "mpi-tools-failure") + set( + required_noexcept_patterns + [=[void[ \t\r\n]+write_text\([^)]*\)[ \t\r\n]+noexcept]=] + [=[valid_count_exchange]=] + [=[auto[ \t\r\n]+expected_abort_state\(\)[ \t\r\n]+noexcept[ \t\r\n]+->[ \t\r\n]+bool]=] + [=[void[ \t\r\n]+observed_abort\([^)]*\)[ \t\r\n]+noexcept]=] + ) + set( + required_compile_time_check_patterns + [=[static_assert *\( *noexcept *\( *mpi_tools_failure_probe::write_text *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *mpi_tools_failure_probe::valid_count_exchange *\([^)]*\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *mpi_tools_failure_probe::expected_abort_state *\( *\) *\) *\) *[;]]=] + [=[static_assert *\( *noexcept *\( *mpi_tools_failure_probe::observed_abort *\([^)]*\) *\) *\) *[;]]=] + ) +else() + message(FATAL_ERROR "unknown PMPI callback audit PROFILE: ${PROFILE}") +endif() +foreach(required_pattern IN LISTS required_noexcept_patterns) + string(REGEX MATCH "${required_pattern}" required_match "${callback_region}") + if(NOT required_match) + string( + APPEND audit_errors + "\n- callback helper is missing its required noexcept contract: ${required_pattern}" + ) + endif() +endforeach() + +string(REGEX REPLACE "[ \t\r\n]+" " " normalized_callback_region "${callback_region}") +foreach(required_check_pattern IN LISTS required_compile_time_check_patterns) + string( + REGEX MATCH "${required_check_pattern}" required_check_match + "${normalized_callback_region}" + ) + if(NOT required_check_match) + string( + APPEND audit_errors + "\n- missing compile-time callback noexcept proof: ${required_check_pattern}" + ) + endif() +endforeach() + +if(NOT audit_errors STREQUAL "") + message( + FATAL_ERROR + "${PROFILE} PMPI callback-safety audit failed:${audit_errors}" + ) +endif() + +message(STATUS "${PROFILE} PMPI callback-safety audit passed") diff --git a/parallel/parallel_src/tests/communication/verify_population_size_broadcast_failure.cmake b/parallel/parallel_src/tests/communication/verify_population_size_broadcast_failure.cmake new file mode 100644 index 00000000..9d7b4bad --- /dev/null +++ b/parallel/parallel_src/tests/communication/verify_population_size_broadcast_failure.cmake @@ -0,0 +1,76 @@ +cmake_minimum_required(VERSION 4.0) + +if( + NOT DEFINED MPIEXEC_EXECUTABLE + OR NOT DEFINED MPIEXEC_NUMPROC_FLAG + OR NOT DEFINED PROBE +) + message(FATAL_ERROR "MPI launcher and population-size PROBE are required") +endif() + +execute_process( + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 2 + ${MPIEXEC_PREFLAGS} + "${PROBE}" + ${MPIEXEC_POSTFLAGS} + RESULT_VARIABLE probe_result + OUTPUT_VARIABLE probe_stdout + ERROR_VARIABLE probe_stderr + TIMEOUT 8 +) + +set(probe_output "${probe_stdout}\n${probe_stderr}") +if("${probe_result}" STREQUAL "0") + message( + FATAL_ERROR + "population-size failure probe returned success unexpectedly\n${probe_output}" + ) +endif() +if("${probe_result}" MATCHES "[Tt]imeout") + message( + FATAL_ERROR + "population-size failure probe timed out instead of aborting\n${probe_output}" + ) +endif() + +string( + FIND + "${probe_output}" + "MPI backend failure: MPI_Bcast(population size)" + diagnostic_offset +) +if(diagnostic_offset EQUAL -1) + message( + FATAL_ERROR + "missing population-size fail-fast diagnostic\n${probe_output}" + ) +endif() + +string( + FIND + "${probe_output}" + "observed population-size MPI_Abort on the affected subcommunicator" + abort_offset +) +if(abort_offset EQUAL -1) + message( + FATAL_ERROR + "missing population-size affected-communicator abort marker\n${probe_output}" + ) +endif() + +foreach( + forbidden_marker + IN ITEMS + "unexpected state" + "returned without fail-fast" +) + string(FIND "${probe_output}" "${forbidden_marker}" forbidden_offset) + if(NOT forbidden_offset EQUAL -1) + message( + FATAL_ERROR + "population-size failure used '${forbidden_marker}'\n${probe_output}" + ) + endif() +endforeach() diff --git a/parallel/parallel_src/tests/distributed_consistency/distributed_consistency_mpi_test.cpp b/parallel/parallel_src/tests/distributed_consistency/distributed_consistency_mpi_test.cpp new file mode 100644 index 00000000..ca4e48f8 --- /dev/null +++ b/parallel/parallel_src/tests/distributed_consistency/distributed_consistency_mpi_test.cpp @@ -0,0 +1,931 @@ +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "data_structure/parallel_graph_access.h" +#include "distributed_partitioning/distributed_consistency.h" +#include "distributed_partitioning/distributed_partitioner.h" +#include "kahip_mpi_capabilities.h" + +namespace consistency_probe { +enum class corruption { + none, + unknown_id, + wrong_value, + duplicate_replacing_missing, + wrong_source, +}; + +inline bool active = false; +inline corruption payload_corruption = corruption::none; +inline bool corruption_fired = false; +inline int topology_create_calls = 0; +inline int count_exchange_calls = 0; +inline int payload_calls = 0; +inline int payload_c_calls = 0; +inline int point_to_point_calls = 0; +inline int immediate_neighbor_calls = 0; +inline int persistent_calls = 0; +inline int completion_calls = 0; +inline int barrier_calls = 0; + +void reset(corruption mode = corruption::none) noexcept { + payload_corruption = mode; + corruption_fired = false; + topology_create_calls = 0; + count_exchange_calls = 0; + payload_calls = 0; + payload_c_calls = 0; + point_to_point_calls = 0; + immediate_neighbor_calls = 0; + persistent_calls = 0; + completion_calls = 0; + barrier_calls = 0; +} + +class activation final { + public: + explicit activation(corruption mode = corruption::none) noexcept { + reset(mode); + active = true; + } + ~activation() noexcept { active = false; } + + activation(activation const&) = delete; + auto operator=(activation const&) -> activation& = delete; +}; + +template +void corrupt_payload(void* receive_buffer, + Count const receive_counts[], + Displacement const receive_displacements[], + MPI_Comm communicator) noexcept { + if (!active || payload_corruption == corruption::none || + receive_buffer == nullptr) { + return; + } + auto rank = 0; + auto indegree = 0; + auto outdegree = 0; + auto weighted = 0; + if (PMPI_Comm_rank(communicator, &rank) != MPI_SUCCESS || rank != 1 || + PMPI_Dist_graph_neighbors_count(communicator, &indegree, &outdegree, + &weighted) != MPI_SUCCESS || + indegree <= 0) { + return; + } + + auto first = -1; + auto second = -1; + for (auto index = 0; index < indegree; ++index) { + if (receive_counts[index] > 0) { + if (first < 0) { + first = index; + } else if (second < 0) { + second = index; + } + } + } + if (first < 0) { + return; + } + + using record = parhip::distributed_consistency::node_value; + auto* records = static_cast(receive_buffer); + auto const first_offset = + static_cast(receive_displacements[first]); + switch (payload_corruption) { + case corruption::unknown_id: + records[first_offset].global_id = + std::numeric_limits::max(); + corruption_fired = true; + break; + case corruption::wrong_value: + records[first_offset].value ^= parhip::NodeID{1}; + corruption_fired = true; + break; + case corruption::duplicate_replacing_missing: + if (receive_counts[first] >= 2) { + records[first_offset + 1] = records[first_offset]; + corruption_fired = true; + } + break; + case corruption::wrong_source: + if (second >= 0) { + auto const second_offset = + static_cast(receive_displacements[second]); + records[first_offset] = records[second_offset]; + corruption_fired = true; + } + break; + case corruption::none: + break; + } +} +} // namespace consistency_probe + +extern "C" int MPI_Dist_graph_create(MPI_Comm communicator, + int source_count, + int const sources[], + int const degrees[], + int const destinations[], + int const weights[], + MPI_Info info, + int reorder, + MPI_Comm* graph_communicator) { + if (consistency_probe::active) { + ++consistency_probe::topology_create_calls; + } + return PMPI_Dist_graph_create(communicator, source_count, sources, degrees, + destinations, weights, info, reorder, + graph_communicator); +} + +extern "C" int MPI_Neighbor_alltoall(void const* send_buffer, + int send_count, + MPI_Datatype send_datatype, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (consistency_probe::active) { + ++consistency_probe::count_exchange_calls; + } + return PMPI_Neighbor_alltoall(send_buffer, send_count, send_datatype, + receive_buffer, receive_count, receive_datatype, + communicator); +} + +extern "C" int MPI_Neighbor_alltoallv(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (consistency_probe::active) { + ++consistency_probe::payload_calls; + } + auto const result = PMPI_Neighbor_alltoallv( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator); + if (result == MPI_SUCCESS) { + consistency_probe::corrupt_payload(receive_buffer, receive_counts, + receive_displacements, communicator); + } + return result; +} + +#if KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C +extern "C" int MPI_Neighbor_alltoallv_c(void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (consistency_probe::active) { + ++consistency_probe::payload_c_calls; + } + auto const result = PMPI_Neighbor_alltoallv_c( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator); + if (result == MPI_SUCCESS) { + consistency_probe::corrupt_payload(receive_buffer, receive_counts, + receive_displacements, communicator); + } + return result; +} +#endif + +#define KAHIP_P2P_WRAPPER(name, signature, arguments) \ + extern "C" int name signature { \ + if (consistency_probe::active) { \ + ++consistency_probe::point_to_point_calls; \ + } \ + return P##name arguments; \ + } + +KAHIP_P2P_WRAPPER(MPI_Send, + (void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator), + (buffer, count, datatype, destination, tag, communicator)) +KAHIP_P2P_WRAPPER(MPI_Ssend, + (void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator), + (buffer, count, datatype, destination, tag, communicator)) +KAHIP_P2P_WRAPPER(MPI_Bsend, + (void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator), + (buffer, count, datatype, destination, tag, communicator)) +KAHIP_P2P_WRAPPER(MPI_Rsend, + (void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator), + (buffer, count, datatype, destination, tag, communicator)) +KAHIP_P2P_WRAPPER( + MPI_Isend, + (void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator, + MPI_Request* request), + (buffer, count, datatype, destination, tag, communicator, request)) +KAHIP_P2P_WRAPPER( + MPI_Issend, + (void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator, + MPI_Request* request), + (buffer, count, datatype, destination, tag, communicator, request)) +KAHIP_P2P_WRAPPER( + MPI_Ibsend, + (void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator, + MPI_Request* request), + (buffer, count, datatype, destination, tag, communicator, request)) +KAHIP_P2P_WRAPPER( + MPI_Irsend, + (void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator, + MPI_Request* request), + (buffer, count, datatype, destination, tag, communicator, request)) +KAHIP_P2P_WRAPPER(MPI_Irecv, + (void* buffer, + int count, + MPI_Datatype datatype, + int source, + int tag, + MPI_Comm communicator, + MPI_Request* request), + (buffer, count, datatype, source, tag, communicator, request)) +KAHIP_P2P_WRAPPER(MPI_Recv, + (void* buffer, + int count, + MPI_Datatype datatype, + int source, + int tag, + MPI_Comm communicator, + MPI_Status* status), + (buffer, count, datatype, source, tag, communicator, status)) +KAHIP_P2P_WRAPPER( + MPI_Probe, + (int source, int tag, MPI_Comm communicator, MPI_Status* status), + (source, tag, communicator, status)) +KAHIP_P2P_WRAPPER( + MPI_Iprobe, + (int source, int tag, MPI_Comm communicator, int* flag, MPI_Status* status), + (source, tag, communicator, flag, status)) +KAHIP_P2P_WRAPPER(MPI_Sendrecv, + (void const* send_buffer, + int send_count, + MPI_Datatype send_datatype, + int destination, + int send_tag, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_datatype, + int source, + int receive_tag, + MPI_Comm communicator, + MPI_Status* status), + (send_buffer, + send_count, + send_datatype, + destination, + send_tag, + receive_buffer, + receive_count, + receive_datatype, + source, + receive_tag, + communicator, + status)) +KAHIP_P2P_WRAPPER(MPI_Sendrecv_replace, + (void* buffer, + int count, + MPI_Datatype datatype, + int destination, + int send_tag, + int source, + int receive_tag, + MPI_Comm communicator, + MPI_Status* status), + (buffer, + count, + datatype, + destination, + send_tag, + source, + receive_tag, + communicator, + status)) +KAHIP_P2P_WRAPPER(MPI_Mprobe, + (int source, + int tag, + MPI_Comm communicator, + MPI_Message* message, + MPI_Status* status), + (source, tag, communicator, message, status)) +KAHIP_P2P_WRAPPER(MPI_Improbe, + (int source, + int tag, + MPI_Comm communicator, + int* flag, + MPI_Message* message, + MPI_Status* status), + (source, tag, communicator, flag, message, status)) +KAHIP_P2P_WRAPPER(MPI_Mrecv, + (void* buffer, + int count, + MPI_Datatype datatype, + MPI_Message* message, + MPI_Status* status), + (buffer, count, datatype, message, status)) +KAHIP_P2P_WRAPPER(MPI_Imrecv, + (void* buffer, + int count, + MPI_Datatype datatype, + MPI_Message* message, + MPI_Request* request), + (buffer, count, datatype, message, request)) + +#undef KAHIP_P2P_WRAPPER + +extern "C" int MPI_Ineighbor_alltoallv(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Request* request) { + if (consistency_probe::active) { + ++consistency_probe::immediate_neighbor_calls; + } + return PMPI_Ineighbor_alltoallv(send_buffer, send_counts, send_displacements, + send_datatype, receive_buffer, receive_counts, + receive_displacements, receive_datatype, + communicator, request); +} + +#if KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV_C +extern "C" int MPI_Ineighbor_alltoallv_c(void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Request* request) { + if (consistency_probe::active) { + ++consistency_probe::immediate_neighbor_calls; + } + return PMPI_Ineighbor_alltoallv_c( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator, request); +} +#endif + +#if KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT +extern "C" int MPI_Neighbor_alltoallv_init(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Info info, + MPI_Request* request) { + if (consistency_probe::active) { + ++consistency_probe::persistent_calls; + } + return PMPI_Neighbor_alltoallv_init( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator, info, request); +} +#endif + +#if KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT_C +extern "C" int MPI_Neighbor_alltoallv_init_c( + void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Info info, + MPI_Request* request) { + if (consistency_probe::active) { + ++consistency_probe::persistent_calls; + } + return PMPI_Neighbor_alltoallv_init_c( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator, info, request); +} +#endif + +extern "C" int MPI_Start(MPI_Request* request) { + if (consistency_probe::active) { + ++consistency_probe::persistent_calls; + } + return PMPI_Start(request); +} + +extern "C" int MPI_Startall(int count, MPI_Request requests[]) { + if (consistency_probe::active) { + ++consistency_probe::persistent_calls; + } + return PMPI_Startall(count, requests); +} + +extern "C" int MPI_Test(MPI_Request* request, + int* complete, + MPI_Status* status) { + if (consistency_probe::active) { + ++consistency_probe::completion_calls; + } + return PMPI_Test(request, complete, status); +} + +extern "C" int MPI_Wait(MPI_Request* request, MPI_Status* status) { + if (consistency_probe::active) { + ++consistency_probe::completion_calls; + } + return PMPI_Wait(request, status); +} + +extern "C" int MPI_Waitall(int count, + MPI_Request requests[], + MPI_Status statuses[]) { + if (consistency_probe::active) { + ++consistency_probe::completion_calls; + } + return PMPI_Waitall(count, requests, statuses); +} + +extern "C" int MPI_Testall(int count, + MPI_Request requests[], + int* complete, + MPI_Status statuses[]) { + if (consistency_probe::active) { + ++consistency_probe::completion_calls; + } + return PMPI_Testall(count, requests, complete, statuses); +} + +extern "C" int MPI_Testany(int count, + MPI_Request requests[], + int* index, + int* complete, + MPI_Status* status) { + if (consistency_probe::active) { + ++consistency_probe::completion_calls; + } + return PMPI_Testany(count, requests, index, complete, status); +} + +extern "C" int MPI_Testsome(int count, + MPI_Request requests[], + int* completed, + int indices[], + MPI_Status statuses[]) { + if (consistency_probe::active) { + ++consistency_probe::completion_calls; + } + return PMPI_Testsome(count, requests, completed, indices, statuses); +} + +extern "C" int MPI_Waitany(int count, + MPI_Request requests[], + int* index, + MPI_Status* status) { + if (consistency_probe::active) { + ++consistency_probe::completion_calls; + } + return PMPI_Waitany(count, requests, index, status); +} + +extern "C" int MPI_Waitsome(int count, + MPI_Request requests[], + int* completed, + int indices[], + MPI_Status statuses[]) { + if (consistency_probe::active) { + ++consistency_probe::completion_calls; + } + return PMPI_Waitsome(count, requests, completed, indices, statuses); +} + +extern "C" int MPI_Request_free(MPI_Request* request) { + if (consistency_probe::active) { + ++consistency_probe::completion_calls; + } + return PMPI_Request_free(request); +} + +extern "C" int MPI_Cancel(MPI_Request* request) { + if (consistency_probe::active) { + ++consistency_probe::completion_calls; + } + return PMPI_Cancel(request); +} + +extern "C" int MPI_Barrier(MPI_Comm communicator) { + if (consistency_probe::active) { + ++consistency_probe::barrier_calls; + } + return PMPI_Barrier(communicator); +} + +namespace { +using parhip::NodeID; +using parhip::parallel_graph_access; + +struct graph_fixture { + std::vector ranges; + std::vector> adjacency; +}; + +[[nodiscard]] auto normal_fixture(int size) -> graph_fixture { + if (size == 1) { + return {{0, 2}, std::vector>(2)}; + } + if (size == 2) { + return {{0, 1, 2}, {{1, 1}, {0, 0}}}; + } + if (size == 3) { + return {{0, 1, 2, 2}, {{1, 1}, {0, 0}}}; + } + if (size == 4) { + return {{0, 1, 2, 3, 4}, {{3, 1}, {0, 2}, {1, 3}, {2, 0}}}; + } + return {{0, 2, 3, 5, 6, 7}, {{1}, {0, 2}, {1, 3}, {2, 4}, {3, 5}, {4}, {}}}; +} + +[[nodiscard]] auto validation_fixture(int size) -> graph_fixture { + if (size == 2) { + return {{0, 2, 4}, {{2}, {3}, {0}, {1}}}; + } + auto ranges = std::vector(static_cast(size) + 1, 5); + ranges[0] = 0; + ranges[1] = 2; + ranges[2] = 3; + ranges[3] = 5; + return {std::move(ranges), {{2}, {2}, {0, 1, 3, 4}, {2}, {2}}}; +} + +[[nodiscard]] auto one_way_fixture(int size) -> graph_fixture { + auto ranges = std::vector(static_cast(size) + 1, 2); + ranges[0] = 0; + ranges[1] = 1; + ranges[2] = 2; + return {std::move(ranges), {{1}, {}}}; +} + +void build_graph(parallel_graph_access& graph, + graph_fixture const& fixture, + int rank) { + auto const first = fixture.ranges[static_cast(rank)]; + auto const end = fixture.ranges[static_cast(rank + 1)]; + auto local_edges = std::size_t{0}; + for (auto global = first; global < end; ++global) { + local_edges += fixture.adjacency[static_cast(global)].size(); + } + auto const global_edges = std::ranges::fold_left( + fixture.adjacency | std::views::transform(&std::vector::size), + std::size_t{0}, std::plus<>{}); + graph.start_construction(end - first, + static_cast(local_edges), + static_cast(fixture.adjacency.size()), + static_cast(global_edges), false); + graph.set_range(first, first == end ? first : end - 1); + auto ranges = fixture.ranges; + graph.set_range_array(ranges); + + for (auto global = first; global < end; ++global) { + auto const local = graph.new_node(); + graph.setNodeWeight(local, 1); + graph.setNodeLabel(local, NodeID{1000} + global); + graph.setSecondPartitionIndex(local, NodeID{2000} + global); + for (auto const target : + fixture.adjacency[static_cast(global)]) { + auto const edge = graph.new_edge(local, target); + graph.setEdgeWeight(edge, 1); + } + } + graph.finish_construction(); + for (auto local = graph.number_of_local_nodes() + NodeID{1}; + local < graph.number_of_local_nodes() + NodeID{1} + + graph.number_of_ghost_nodes(); + ++local) { + auto const global = graph.getGlobalID(local); + graph.setNodeLabel(local, NodeID{1000} + global); + graph.setSecondPartitionIndex(local, NodeID{2000} + global); + } +} + +void require_common(bool local_condition) { + auto const local = local_condition ? 1 : 0; + auto common = 0; + REQUIRE(PMPI_Allreduce(&local, &common, 1, MPI_INT, MPI_MIN, + MPI_COMM_WORLD) == MPI_SUCCESS); + REQUIRE(common == 1); +} + +template +void require_common_error(Operation&& operation, + std::string_view expected_context, + int size) { + auto caught = 0; + auto structured = 0; + auto context_matches = 0; + try { + std::invoke(std::forward(operation)); + } catch (parhip::mpi::mpi_error const& error) { + caught = 1; + structured = 1; + context_matches = + error.context().find(expected_context) != std::string_view::npos ? 1 + : 0; + } catch (...) { + caught = 1; + } + auto caught_total = 0; + auto structured_total = 0; + auto context_total = 0; + REQUIRE(PMPI_Allreduce(&caught, &caught_total, 1, MPI_INT, MPI_SUM, + MPI_COMM_WORLD) == MPI_SUCCESS); + REQUIRE(PMPI_Allreduce(&structured, &structured_total, 1, MPI_INT, MPI_SUM, + MPI_COMM_WORLD) == MPI_SUCCESS); + REQUIRE(PMPI_Allreduce(&context_matches, &context_total, 1, MPI_INT, MPI_SUM, + MPI_COMM_WORLD) == MPI_SUCCESS); + REQUIRE(caught_total == size); + REQUIRE(structured_total == size); + REQUIRE(context_total == size); +} + +struct snapshot_entry { + NodeID global_id; + NodeID label; + NodeID second; + + auto operator==(snapshot_entry const&) const -> bool = default; +}; + +[[nodiscard]] auto snapshot(parallel_graph_access& graph) + -> std::vector { + auto result = std::vector{}; + result.reserve(static_cast(graph.number_of_local_nodes() + + graph.number_of_ghost_nodes())); + for (NodeID local = 0; local < graph.number_of_local_nodes(); ++local) { + result.push_back({graph.getGlobalID(local), graph.getNodeLabel(local), + graph.getSecondPartitionIndex(local)}); + } + for (auto local = graph.number_of_local_nodes() + NodeID{1}; + local < graph.number_of_local_nodes() + NodeID{1} + + graph.number_of_ghost_nodes(); + ++local) { + result.push_back({graph.getGlobalID(local), graph.getNodeLabel(local), + graph.getSecondPartitionIndex(local)}); + } + return result; +} + +[[nodiscard]] auto protocol_is_blocking_collective(int expected_operations, + int expected_topologies) + -> bool { +#if KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C + auto const payload_path_is_valid = + consistency_probe::payload_calls == 0 && + consistency_probe::payload_c_calls == expected_operations; +#else + auto const payload_path_is_valid = + consistency_probe::payload_calls == expected_operations && + consistency_probe::payload_c_calls == 0; +#endif + return consistency_probe::topology_create_calls == expected_topologies && + consistency_probe::count_exchange_calls == expected_operations && + payload_path_is_valid && + consistency_probe::point_to_point_calls == 0 && + consistency_probe::immediate_neighbor_calls == 0 && + consistency_probe::persistent_calls == 0 && + consistency_probe::completion_calls == 0 && + consistency_probe::barrier_calls == 0; +} +} // namespace + +TEST_CASE("consistency checks use one cached blocking neighborhood plan", + "[unit][mpi][distributed-consistency][protocol]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + parallel_graph_access graph{MPI_COMM_WORLD}; + build_graph(graph, normal_fixture(size), rank); + parhip::distributed_partitioner partitioner; + parhip::PPartitionConfig config; + + { + auto probe = consistency_probe::activation{}; + partitioner.check_labels(MPI_COMM_WORLD, config, graph); + partitioner.check(MPI_COMM_WORLD, config, graph); + partitioner.check_labels(MPI_COMM_WORLD, config, graph); + require_common(protocol_is_blocking_collective(3, 1)); + } +} + +TEST_CASE("reconstruction invalidates exactly one cached topology", + "[unit][mpi][distributed-consistency][rebuild]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + parallel_graph_access graph{MPI_COMM_WORLD}; + build_graph(graph, normal_fixture(size), rank); + parhip::distributed_partitioner partitioner; + parhip::PPartitionConfig config; + + { + auto probe = consistency_probe::activation{}; + partitioner.check_labels(MPI_COMM_WORLD, config, graph); + build_graph(graph, normal_fixture(size), rank); + partitioner.check(MPI_COMM_WORLD, config, graph); + require_common(protocol_is_blocking_collective(2, 2)); + } +} + +TEST_CASE("asymmetric graph plans fail before count or payload traffic", + "[unit][mpi][distributed-consistency][asymmetric]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size == 1) { + return; + } + parallel_graph_access graph{MPI_COMM_WORLD}; + build_graph(graph, one_way_fixture(size), rank); + parhip::distributed_partitioner partitioner; + parhip::PPartitionConfig config; + + { + auto probe = consistency_probe::activation{}; + require_common_error( + [&] { partitioner.check_labels(MPI_COMM_WORLD, config, graph); }, + "ghost exchange plan semantic validation", size); + require_common(consistency_probe::topology_create_calls == 1 && + consistency_probe::count_exchange_calls == 0 && + consistency_probe::payload_calls == 0 && + consistency_probe::payload_c_calls == 0 && + consistency_probe::point_to_point_calls == 0); + } +} + +TEST_CASE("congruent communicators are accepted and similar ones rejected", + "[unit][mpi][distributed-consistency][communicator]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + parallel_graph_access graph{MPI_COMM_WORLD}; + build_graph(graph, normal_fixture(size), rank); + parhip::distributed_partitioner partitioner; + parhip::PPartitionConfig config; + MPI_Comm duplicate = MPI_COMM_NULL; + REQUIRE(MPI_Comm_dup(MPI_COMM_WORLD, &duplicate) == MPI_SUCCESS); + partitioner.check_labels(duplicate, config, graph); + REQUIRE(MPI_Comm_free(&duplicate) == MPI_SUCCESS); + + if (size > 1) { + MPI_Comm similar = MPI_COMM_NULL; + REQUIRE(MPI_Comm_split(MPI_COMM_WORLD, 0, size - rank, &similar) == + MPI_SUCCESS); + { + auto probe = consistency_probe::activation{}; + require_common_error( + [&] { partitioner.check_labels(similar, config, graph); }, + "communicator", size); + require_common(consistency_probe::count_exchange_calls == 0 && + consistency_probe::payload_calls == 0 && + consistency_probe::payload_c_calls == 0); + } + REQUIRE(MPI_Comm_free(&similar) == MPI_SUCCESS); + } +} + +TEST_CASE("received corruption is common mutation-free and recoverable", + "[unit][mpi][distributed-consistency][validation]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size == 1) { + return; + } + + auto const modes = + std::array{consistency_probe::corruption::unknown_id, + consistency_probe::corruption::wrong_value, + consistency_probe::corruption::duplicate_replacing_missing, + consistency_probe::corruption::wrong_source}; + for (auto const mode : modes) { + if (mode == consistency_probe::corruption::wrong_source && size < 3) { + continue; + } + parallel_graph_access graph{MPI_COMM_WORLD}; + build_graph(graph, validation_fixture(size), rank); + parhip::distributed_partitioner partitioner; + parhip::PPartitionConfig config; + auto const before = snapshot(graph); + auto const unknown = std::numeric_limits::max(); + require_common(!graph.find_ghost_local_id(unknown, 0).has_value()); + + { + auto probe = consistency_probe::activation{mode}; + auto const second_partition = + mode == consistency_probe::corruption::wrong_value; + require_common_error( + [&] { + if (second_partition) { + partitioner.check(MPI_COMM_WORLD, config, graph); + } else { + partitioner.check_labels(MPI_COMM_WORLD, config, graph); + } + }, + second_partition ? "second-partition consistency" + : "label consistency", + size); + auto local_fired = consistency_probe::corruption_fired ? 1 : 0; + auto fired = 0; + REQUIRE(PMPI_Allreduce(&local_fired, &fired, 1, MPI_INT, MPI_MAX, + MPI_COMM_WORLD) == MPI_SUCCESS); + require_common(fired == 1 && snapshot(graph) == before && + !graph.find_ghost_local_id(unknown, 0).has_value()); + + consistency_probe::payload_corruption = + consistency_probe::corruption::none; + if (second_partition) { + partitioner.check(MPI_COMM_WORLD, config, graph); + } else { + partitioner.check_labels(MPI_COMM_WORLD, config, graph); + } + require_common(protocol_is_blocking_collective(2, 1)); + } + } +} diff --git a/parallel/parallel_src/tests/distributed_consistency/ghost_label_exchange_mpi_test.cpp b/parallel/parallel_src/tests/distributed_consistency/ghost_label_exchange_mpi_test.cpp new file mode 100644 index 00000000..8f4e5e4a --- /dev/null +++ b/parallel/parallel_src/tests/distributed_consistency/ghost_label_exchange_mpi_test.cpp @@ -0,0 +1,1012 @@ +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "communication/ghost_label_update.h" +#include "communication/mpi_error.h" +#include "communication/mpi_trace.h" +#include "data_structure/parallel_graph_access.h" +#include "kahip_mpi_capabilities.h" +#include "partition_config.h" + +// KAHIP_PMPI_CALLBACK_REGION_BEGIN +namespace ghost_label_probe { +enum class corruption { + none, + unknown_id, + wrong_source, +}; + +struct counters { + int topology_creations = 0; + int count_exchanges = 0; + int blocking_payloads = 0; + int immediate_payloads = 0; + int completions = 0; + int point_to_point_calls = 0; + int barriers = 0; + std::uint64_t immediate_records = 0; + bool callback_error = false; + + auto operator==(counters const&) const -> bool = default; +}; + +using wire_record = parhip::ghost_label_update; + +inline bool active = false; +inline corruption corruption_mode = corruption::none; +inline bool corruption_fired = false; +inline counters observed{}; + +void reset(corruption mode = corruption::none) noexcept { + corruption_mode = mode; + corruption_fired = false; + observed = {}; +} + +class activation final { + public: + explicit activation(corruption mode = corruption::none) noexcept { + reset(mode); + active = true; + } + + ~activation() noexcept { active = false; } + + activation(activation const&) = delete; + auto operator=(activation const&) -> activation& = delete; +}; + +template +void corrupt_neighbor_payload(void* receive_buffer, + Count const receive_counts[], + Displacement const receive_displacements[], + MPI_Comm communicator) noexcept { + if (!active || corruption_mode == corruption::none || corruption_fired || + receive_buffer == nullptr) { + return; + } + + auto rank = 0; + auto indegree = 0; + auto outdegree = 0; + auto weighted = 0; + if (PMPI_Comm_rank(communicator, &rank) != MPI_SUCCESS || rank != 1 || + PMPI_Dist_graph_neighbors_count(communicator, &indegree, &outdegree, + &weighted) != MPI_SUCCESS || + indegree == 0) { + return; + } + + auto first = -1; + auto second = -1; + for (auto index = 0; index < indegree; ++index) { + if (receive_counts[index] <= 0) { + continue; + } + if (first < 0) { + first = index; + } else if (second < 0) { + second = index; + } + } + if (first < 0) { + return; + } + + auto* records = static_cast(receive_buffer); + auto const first_offset = + static_cast(receive_displacements[first]); + if (corruption_mode == corruption::unknown_id) { + records[first_offset].global_id = + std::numeric_limits::max(); + corruption_fired = true; + return; + } + if (second >= 0) { + auto const second_offset = + static_cast(receive_displacements[second]); + records[first_offset].global_id = records[second_offset].global_id; + corruption_fired = true; + } +} + +void corrupt_legacy_payload(void* receive_buffer, + int count, + int source, + MPI_Comm communicator) noexcept { + if (!active || corruption_mode == corruption::none || corruption_fired || + receive_buffer == nullptr || count < 2) { + return; + } + auto rank = 0; + auto size = 0; + if (PMPI_Comm_rank(communicator, &rank) != MPI_SUCCESS || + PMPI_Comm_size(communicator, &size) != MPI_SUCCESS || rank != 1) { + return; + } + auto* words = static_cast(receive_buffer); + if (corruption_mode == corruption::unknown_id) { + words[0] = std::numeric_limits::max(); + corruption_fired = true; + return; + } + if (size >= 3 && (source == 0 || source == 2)) { + words[0] = static_cast(source == 0 ? 2 : 0); + corruption_fired = true; + } +} + +template +void observe_immediate_payload(Count const send_counts[], + MPI_Comm communicator) noexcept { + if (!active) { + return; + } + auto indegree = 0; + auto outdegree = 0; + auto weighted = 0; + if (PMPI_Dist_graph_neighbors_count(communicator, &indegree, &outdegree, + &weighted) != MPI_SUCCESS || + outdegree < 0 || (outdegree != 0 && send_counts == nullptr)) { + observed.callback_error = true; + return; + } + for (auto index = 0; index < outdegree; ++index) { + if (send_counts[index] < 0 || + !std::in_range(send_counts[index]) || + observed.immediate_records > + std::numeric_limits::max() - + static_cast(send_counts[index])) { + observed.callback_error = true; + return; + } + observed.immediate_records += + static_cast(send_counts[index]); + } +} +} // namespace ghost_label_probe + +static_assert(noexcept( + ghost_label_probe::corrupt_neighbor_payload(nullptr, + nullptr, + nullptr, + MPI_COMM_NULL))); +static_assert( + noexcept(ghost_label_probe::corrupt_neighbor_payload( + nullptr, + nullptr, + nullptr, + MPI_COMM_NULL))); +static_assert(noexcept( + ghost_label_probe::corrupt_legacy_payload(nullptr, 0, 0, MPI_COMM_NULL))); +static_assert(noexcept( + ghost_label_probe::observe_immediate_payload(nullptr, MPI_COMM_NULL))); +static_assert(noexcept( + ghost_label_probe::observe_immediate_payload(nullptr, + MPI_COMM_NULL))); + +extern "C" int MPI_Dist_graph_create(MPI_Comm communicator, + int source_count, + int const sources[], + int const degrees[], + int const destinations[], + int const weights[], + MPI_Info info, + int reorder, + MPI_Comm* graph_communicator) { + if (ghost_label_probe::active) { + ++ghost_label_probe::observed.topology_creations; + } + return PMPI_Dist_graph_create(communicator, source_count, sources, degrees, + destinations, weights, info, reorder, + graph_communicator); +} + +extern "C" int MPI_Neighbor_alltoall(void const* send_buffer, + int send_count, + MPI_Datatype send_datatype, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (ghost_label_probe::active) { + ++ghost_label_probe::observed.count_exchanges; + } + return PMPI_Neighbor_alltoall(send_buffer, send_count, send_datatype, + receive_buffer, receive_count, receive_datatype, + communicator); +} + +extern "C" int MPI_Neighbor_alltoallv(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (ghost_label_probe::active) { + ++ghost_label_probe::observed.blocking_payloads; + } + auto const result = PMPI_Neighbor_alltoallv( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator); + if (result == MPI_SUCCESS) { + ghost_label_probe::corrupt_neighbor_payload( + receive_buffer, receive_counts, receive_displacements, communicator); + } + return result; +} + +#if KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C +extern "C" int MPI_Neighbor_alltoallv_c(void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (ghost_label_probe::active) { + ++ghost_label_probe::observed.blocking_payloads; + } + auto const result = PMPI_Neighbor_alltoallv_c( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator); + if (result == MPI_SUCCESS) { + ghost_label_probe::corrupt_neighbor_payload( + receive_buffer, receive_counts, receive_displacements, communicator); + } + return result; +} +#endif + +extern "C" int MPI_Ineighbor_alltoallv(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Request* request) { + if (ghost_label_probe::active) { + ++ghost_label_probe::observed.immediate_payloads; + ghost_label_probe::observe_immediate_payload(send_counts, communicator); + } + return PMPI_Ineighbor_alltoallv(send_buffer, send_counts, send_displacements, + send_datatype, receive_buffer, receive_counts, + receive_displacements, receive_datatype, + communicator, request); +} + +#if KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV_C +extern "C" int MPI_Ineighbor_alltoallv_c(void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Request* request) { + if (ghost_label_probe::active) { + ++ghost_label_probe::observed.immediate_payloads; + ghost_label_probe::observe_immediate_payload(send_counts, communicator); + } + return PMPI_Ineighbor_alltoallv_c( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator, request); +} +#endif + +extern "C" int MPI_Isend(void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator, + MPI_Request* request) { + if (ghost_label_probe::active) { + ++ghost_label_probe::observed.point_to_point_calls; + } + return PMPI_Isend(buffer, count, datatype, destination, tag, communicator, + request); +} + +extern "C" int MPI_Probe(int source, + int tag, + MPI_Comm communicator, + MPI_Status* status) { + if (ghost_label_probe::active) { + ++ghost_label_probe::observed.point_to_point_calls; + } + return PMPI_Probe(source, tag, communicator, status); +} + +extern "C" int MPI_Get_count(MPI_Status const* status, + MPI_Datatype datatype, + int* count) { + if (ghost_label_probe::active) { + ++ghost_label_probe::observed.point_to_point_calls; + } + return PMPI_Get_count(status, datatype, count); +} + +extern "C" int MPI_Recv(void* buffer, + int count, + MPI_Datatype datatype, + int source, + int tag, + MPI_Comm communicator, + MPI_Status* status) { + if (ghost_label_probe::active) { + ++ghost_label_probe::observed.point_to_point_calls; + } + auto const result = + PMPI_Recv(buffer, count, datatype, source, tag, communicator, status); + if (result == MPI_SUCCESS) { + ghost_label_probe::corrupt_legacy_payload(buffer, count, source, + communicator); + } + return result; +} + +extern "C" int MPI_Wait(MPI_Request* request, MPI_Status* status) { + if (ghost_label_probe::active) { + ++ghost_label_probe::observed.completions; + } + return PMPI_Wait(request, status); +} + +extern "C" int MPI_Barrier(MPI_Comm communicator) { + if (ghost_label_probe::active) { + ++ghost_label_probe::observed.barriers; + } + return PMPI_Barrier(communicator); +} +// KAHIP_PMPI_CALLBACK_REGION_END + +namespace { +using parhip::NodeID; +using parhip::parallel_graph_access; + +struct graph_fixture { + std::vector ranges; + std::vector> adjacency; +}; + +[[nodiscard]] auto ring_fixture(int size) -> graph_fixture { + auto ranges = std::vector(static_cast(size) + 1); + std::ranges::iota(ranges, NodeID{0}); + auto adjacency = + std::vector>(static_cast(size)); + if (size > 1) { + for (auto rank = 0; rank < size; ++rank) { + auto& neighbors = adjacency[static_cast(rank)]; + neighbors = {static_cast((rank + size - 1) % size), + static_cast((rank + 1) % size)}; + std::ranges::sort(neighbors); + auto const unique_end = std::ranges::unique(neighbors); + neighbors.erase(unique_end.begin(), unique_end.end()); + } + } + return {std::move(ranges), std::move(adjacency)}; +} + +[[nodiscard]] auto zero_local_fixture(int size) -> graph_fixture { + auto ranges = std::vector(static_cast(size) + 1, 2); + ranges[0] = 0; + ranges[1] = 1; + return {std::move(ranges), {{1}, {0}}}; +} + +[[nodiscard]] auto multi_node_ring_fixture(int size, NodeID nodes_per_rank) + -> graph_fixture { + auto ranges = std::vector(static_cast(size) + 1); + std::ranges::transform( + std::views::iota(0, size + 1), ranges.begin(), + [&](int rank) { return static_cast(rank) * nodes_per_rank; }); + auto adjacency = std::vector>( + static_cast(size) * nodes_per_rank); + if (size > 1) { + for (auto rank = 0; rank < size; ++rank) { + for (NodeID lane = 0; lane < nodes_per_rank; ++lane) { + auto& neighbors = adjacency[static_cast( + static_cast(rank) * nodes_per_rank + lane)]; + neighbors = { + static_cast((rank + size - 1) % size) * nodes_per_rank + + lane, + static_cast((rank + 1) % size) * nodes_per_rank + lane, + }; + std::ranges::sort(neighbors); + auto const unique_end = std::ranges::unique(neighbors); + neighbors.erase(unique_end.begin(), unique_end.end()); + } + } + } + return {std::move(ranges), std::move(adjacency)}; +} + +void build_graph(parallel_graph_access& graph, + graph_fixture const& fixture, + int rank, + NodeID label_base, + bool configure_incremental_rounds = false) { + auto const first = fixture.ranges[static_cast(rank)]; + auto const end = fixture.ranges[static_cast(rank + 1)]; + auto local_edges = std::size_t{0}; + for (auto global = first; global < end; ++global) { + local_edges += fixture.adjacency[static_cast(global)].size(); + } + auto const global_edges = std::ranges::fold_left( + fixture.adjacency | std::views::transform(&std::vector::size), + std::size_t{0}, std::plus<>{}); + graph.start_construction( + end - first, static_cast(local_edges), + static_cast(fixture.adjacency.size()), + static_cast(global_edges), configure_incremental_rounds); + graph.set_range(first, first == end ? first : end - 1); + auto ranges = fixture.ranges; + graph.set_range_array(ranges); + + for (auto global = first; global < end; ++global) { + auto const local = graph.new_node(); + graph.setNodeWeight(local, 1); + graph.setNodeLabel(local, label_base + global); + graph.setSecondPartitionIndex(local, 0); + for (auto const target : + fixture.adjacency[static_cast(global)]) { + auto const edge = graph.new_edge(local, target); + graph.setEdgeWeight(edge, 1); + } + } + graph.finish_construction(); +} + +[[nodiscard]] auto graph_labels(parallel_graph_access& graph) + -> std::vector> { + auto result = std::vector>{}; + for (NodeID local = 0; local < graph.number_of_local_nodes(); ++local) { + result.emplace_back(graph.getGlobalID(local), graph.getNodeLabel(local)); + } + for (auto local = graph.number_of_local_nodes() + NodeID{1}; + local < graph.number_of_local_nodes() + NodeID{1} + + graph.number_of_ghost_nodes(); + ++local) { + result.emplace_back(graph.getGlobalID(local), graph.getNodeLabel(local)); + } + std::ranges::sort(result); + return result; +} + +[[nodiscard]] auto labels_are_exact(parallel_graph_access& graph, + NodeID label_base) -> bool { + return std::ranges::all_of(graph_labels(graph), [&](auto const& entry) { + return entry.second == label_base + entry.first; + }); +} + +[[nodiscard]] auto ghost_labels_are_exact(parallel_graph_access& graph, + NodeID label_base) -> bool { + for (auto local = graph.number_of_local_nodes() + NodeID{1}; + local < graph.number_of_local_nodes() + NodeID{1} + + graph.number_of_ghost_nodes(); + ++local) { + if (graph.getNodeLabel(local) != label_base + graph.getGlobalID(local)) { + return false; + } + } + return true; +} + +void require_common(bool local_condition) { + auto const local = local_condition ? 1 : 0; + auto common = 0; + REQUIRE(PMPI_Allreduce(&local, &common, 1, MPI_INT, MPI_MIN, + MPI_COMM_WORLD) == MPI_SUCCESS); + CHECK(common == 1); +} + +[[nodiscard]] auto global_protocol_is_collective( + ghost_label_probe::counters const& value) -> bool { + return value.topology_creations == 1 && value.count_exchanges == 1 && + value.blocking_payloads == 1 && value.immediate_payloads == 0 && + value.completions == 0 && value.point_to_point_calls == 0 && + value.barriers == 0; +} + +class trace_activation final { + public: + trace_activation() { + parhip::mpi::trace::reset(); + parhip::mpi::trace::set_active(true); + KAHIP_MPI_TRACE_SET_HIERARCHY(7, 3, parhip::mpi::trace::epoch::refinement); + KAHIP_MPI_TRACE_SET_ITERATION(11); + } + ~trace_activation() { parhip::mpi::trace::set_active(false); } + + trace_activation(trace_activation const&) = delete; + auto operator=(trace_activation const&) -> trace_activation& = delete; +}; + +[[nodiscard]] auto trace_preserves_repeated_updates( + std::span records, + int rank, + int size) -> bool { + auto expected_sources = std::vector{}; + if (size > 1) { + expected_sources = {(rank + size - 1) % size, (rank + 1) % size}; + std::ranges::sort(expected_sources); + auto const unique_end = std::ranges::unique(expected_sources); + expected_sources.erase(unique_end.begin(), unique_end.end()); + } + for (auto const source : expected_sources) { + auto source_records = std::vector{}; + std::ranges::copy_if( + records, std::back_inserter(source_records), [&](auto const& record) { + return record.stage_id == parhip::mpi::trace::stage::ghost_update && + record.global_id == static_cast(source); + }); + if (source_records.size() != 3 || + source_records[0].payload != "label=" + std::to_string(100 + source) || + source_records[1].payload != "label=" + std::to_string(200 + source) || + source_records[2].payload != "label=" + std::to_string(300 + source) || + source_records[0].hierarchy.round != 2 || + source_records[1].hierarchy.round != 3 || + source_records[2].hierarchy.round != 3) { + return false; + } + } + return true; +} + +[[nodiscard]] auto trace_matches_public_scheduler( + std::span records, + parallel_graph_access& graph, + int rank) -> bool { +#if KAHIP_ENABLE_MPI_TRACE + auto ghost_records = std::vector{}; + std::ranges::copy_if( + records, std::back_inserter(ghost_records), [](auto const& record) { + return record.stage_id == parhip::mpi::trace::stage::ghost_update; + }); + if (ghost_records.size() != graph.number_of_ghost_nodes()) { + return false; + } + std::ranges::sort(ghost_records, {}, &parhip::mpi::trace::record::global_id); + auto expected_global_ids = std::vector{}; + for (auto local = graph.number_of_local_nodes() + NodeID{1}; + local < graph.number_of_local_nodes() + NodeID{1} + + graph.number_of_ghost_nodes(); + ++local) { + expected_global_ids.push_back(graph.getGlobalID(local)); + } + std::ranges::sort(expected_global_ids); + for (std::size_t index = 0; index < ghost_records.size(); ++index) { + auto const global_id = expected_global_ids[index]; + if (!std::in_range(global_id / NodeID{4})) { + return false; + } + auto const& record = ghost_records[index]; + auto const expected_round = global_id % NodeID{4} < NodeID{2} ? 2U : 3U; + auto const expected_owner = static_cast(global_id / NodeID{4}); + if (record.global_id != global_id || record.hierarchy.cycle != 7 || + record.hierarchy.level != 3 || + record.hierarchy.epoch_id != parhip::mpi::trace::epoch::refinement || + record.hierarchy.iteration != 11 || + record.hierarchy.round != expected_round || + record.actors.owner != expected_owner || + record.actors.requester != -1 || record.actors.receiver != rank || + record.semantic_key != "label" || + record.payload != "label=" + std::to_string(NodeID{100} + global_id)) { + return false; + } + } + return true; +#else + static_cast(graph); + static_cast(rank); + return records.empty(); +#endif +} + +[[nodiscard]] auto ghost_balances_match_public_scheduler( + parallel_graph_access& graph) -> bool { + for (auto local = graph.number_of_local_nodes() + NodeID{1}; + local < graph.number_of_local_nodes() + NodeID{1} + + graph.number_of_ghost_nodes(); + ++local) { + auto const global_id = graph.getGlobalID(local); + if (graph.getBlockSize(global_id) != 0 || + graph.getBlockSize(NodeID{100} + global_id) != 1) { + return false; + } + } + return true; +} + +[[nodiscard]] auto trace_preserves_queue_across_global( + std::span records, + int rank, + int size) -> bool { +#if KAHIP_ENABLE_MPI_TRACE + auto expected_sources = std::vector{}; + if (size > 1) { + expected_sources = {(rank + size - 1) % size, (rank + 1) % size}; + std::ranges::sort(expected_sources); + auto const unique_end = std::ranges::unique(expected_sources); + expected_sources.erase(unique_end.begin(), unique_end.end()); + } + for (auto const source : expected_sources) { + auto source_records = std::vector{}; + std::ranges::copy_if( + records, std::back_inserter(source_records), [&](auto const& record) { + return record.stage_id == parhip::mpi::trace::stage::ghost_update && + record.global_id == static_cast(source); + }); + if (source_records.size() != 3 || + source_records[0].payload != "label=" + std::to_string(100 + source) || + source_records[0].hierarchy.round != 0 || + source_records[1].payload != "label=" + std::to_string(100 + source) || + source_records[1].hierarchy.round != 2 || + source_records[2].payload != "label=" + std::to_string(200 + source) || + source_records[2].hierarchy.round != 2) { + return false; + } + } + return true; +#else + static_cast(rank); + static_cast(size); + return records.empty(); +#endif +} + +void exercise_corruption(ghost_label_probe::corruption mode) { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size < 3) { + return; + } + + constexpr auto label_base = NodeID{100}; + parallel_graph_access graph{MPI_COMM_WORLD}; + build_graph(graph, ring_fixture(size), rank, label_base); + auto config = parhip::PPartitionConfig{}; + config.k = 1024; + config.total_num_labels = 1024; + graph.init_balance_management(config); + auto const before = graph_labels(graph); + + auto caught = 0; + auto structured = 0; + auto after_failure = std::vector>{}; + auto retry_is_exact = false; + auto fired = false; + { + auto probe = ghost_label_probe::activation{mode}; + try { + graph.update_ghost_node_data_global(); + } catch (parhip::mpi::mpi_error const&) { + caught = 1; + structured = 1; + } catch (...) { + caught = 1; + } + after_failure = graph_labels(graph); + fired = ghost_label_probe::corruption_fired; + ghost_label_probe::corruption_mode = ghost_label_probe::corruption::none; + + auto caught_minimum = 0; + REQUIRE(PMPI_Allreduce(&caught, &caught_minimum, 1, MPI_INT, MPI_MIN, + MPI_COMM_WORLD) == MPI_SUCCESS); + if (caught_minimum == 0) { + for (NodeID local = 0; local < graph.number_of_local_nodes(); ++local) { + graph.setNodeLabel(local, label_base + graph.getGlobalID(local)); + } + graph.update_ghost_node_data_finish(); + } + + graph.update_ghost_node_data_global(); + retry_is_exact = labels_are_exact(graph, label_base); + } + + auto caught_total = 0; + auto structured_total = 0; + auto fired_local = fired ? 1 : 0; + auto fired_anywhere = 0; + REQUIRE(PMPI_Allreduce(&caught, &caught_total, 1, MPI_INT, MPI_SUM, + MPI_COMM_WORLD) == MPI_SUCCESS); + REQUIRE(PMPI_Allreduce(&structured, &structured_total, 1, MPI_INT, MPI_SUM, + MPI_COMM_WORLD) == MPI_SUCCESS); + REQUIRE(PMPI_Allreduce(&fired_local, &fired_anywhere, 1, MPI_INT, MPI_MAX, + MPI_COMM_WORLD) == MPI_SUCCESS); + CAPTURE(caught_total, structured_total, fired_anywhere, + after_failure == before, retry_is_exact); + require_common(fired_anywhere == 1 && caught_total == size && + structured_total == size && after_failure == before && + retry_is_exact); +} +} // namespace + +TEST_CASE("ghost label wire records have exact MPI extent", + "[unit][mpi][ghost-label][datatype]") { + STATIC_REQUIRE(std::is_standard_layout_v); + STATIC_REQUIRE(std::is_trivially_copyable_v); + auto datatype = parhip::mpi::make_mpi_datatype( + MPI_COMM_WORLD); + auto lower_bound = MPI_Aint{-1}; + auto extent = MPI_Aint{-1}; + REQUIRE(PMPI_Type_get_extent(datatype.native_handle(), &lower_bound, + &extent) == MPI_SUCCESS); + REQUIRE(lower_bound == 0); + REQUIRE(extent == static_cast(sizeof(parhip::ghost_label_update))); +} + +TEST_CASE("global ghost labels use one blocking neighborhood exchange", + "[unit][mpi][ghost-label][protocol]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + + constexpr auto label_base = NodeID{100}; + parallel_graph_access graph{MPI_COMM_WORLD}; + build_graph(graph, ring_fixture(size), rank, label_base); + auto protocol = ghost_label_probe::counters{}; + auto exact = false; + { + auto probe = ghost_label_probe::activation{}; + graph.update_ghost_node_data_global(); + protocol = ghost_label_probe::observed; + exact = labels_are_exact(graph, label_base); + } + CAPTURE(exact, protocol.topology_creations, protocol.count_exchanges, + protocol.blocking_payloads, protocol.immediate_payloads, + protocol.completions, protocol.point_to_point_calls, + protocol.barriers); + require_common(exact && global_protocol_is_collective(protocol)); +} + +TEST_CASE("global ghost labels include ranks with zero local work", + "[unit][mpi][ghost-label][zero-local]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size < 3) { + return; + } + + constexpr auto label_base = NodeID{100}; + parallel_graph_access graph{MPI_COMM_WORLD}; + build_graph(graph, zero_local_fixture(size), rank, label_base); + auto protocol = ghost_label_probe::counters{}; + auto exact = false; + { + auto probe = ghost_label_probe::activation{}; + graph.update_ghost_node_data_global(); + protocol = ghost_label_probe::observed; + exact = labels_are_exact(graph, label_base); + } + CAPTURE(exact, protocol.topology_creations, protocol.count_exchanges, + protocol.blocking_payloads, protocol.immediate_payloads, + protocol.completions, protocol.point_to_point_calls, + protocol.barriers); + require_common(exact && global_protocol_is_collective(protocol)); +} + +TEST_CASE("incremental ghost labels preserve one-round lag and update order", + "[unit][mpi][ghost-label][pipeline]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size == 1) { + return; + } + + parallel_graph_access::set_comm_rounds(8); + parallel_graph_access graph{MPI_COMM_WORLD}; + build_graph(graph, ring_fixture(size), rank, NodeID{0}, true); + auto config = parhip::PPartitionConfig{}; + config.k = 1024; + config.total_num_labels = 1024; + graph.init_balance_management(config); + + auto after_first = ghost_label_probe::counters{}; + auto after_second = ghost_label_probe::counters{}; + auto after_third = ghost_label_probe::counters{}; + auto first_post_did_not_apply = false; + auto second_completed_first = false; + auto third_preserved_order = false; + auto trace_is_exact = KAHIP_ENABLE_MPI_TRACE == 0; + { + auto probe = ghost_label_probe::activation{}; + auto trace = trace_activation{}; + + graph.setNodeLabel(0, static_cast(100 + rank)); + graph.update_ghost_node_data(false); + after_first = ghost_label_probe::observed; + first_post_did_not_apply = ghost_labels_are_exact(graph, NodeID{0}); + + graph.setNodeLabel(0, static_cast(200 + rank)); + graph.setNodeLabel(0, static_cast(300 + rank)); + graph.update_ghost_node_data(false); + after_second = ghost_label_probe::observed; + second_completed_first = ghost_labels_are_exact(graph, NodeID{100}); + + graph.update_ghost_node_data(false); + after_third = ghost_label_probe::observed; + third_preserved_order = ghost_labels_are_exact(graph, NodeID{300}); +#if KAHIP_ENABLE_MPI_TRACE + auto const records = parhip::mpi::trace::snapshot(); + trace_is_exact = trace_preserves_repeated_updates(records, rank, size); +#endif + + graph.update_ghost_node_data_finish(); + } + + auto const protocol_is_exact = + after_first.topology_creations == 1 && after_first.count_exchanges == 1 && + after_first.immediate_payloads == 1 && after_first.completions == 0 && + after_first.point_to_point_calls == 0 && + after_second.count_exchanges == 2 && + after_second.immediate_payloads == 2 && after_second.completions == 1 && + after_second.point_to_point_calls == 0 && + after_third.count_exchanges == 3 && after_third.immediate_payloads == 3 && + after_third.completions == 2 && after_third.point_to_point_calls == 0; + CAPTURE(first_post_did_not_apply, second_completed_first, + third_preserved_order, trace_is_exact, protocol_is_exact, + after_first.topology_creations, after_first.count_exchanges, + after_first.immediate_payloads, after_first.completions, + after_first.point_to_point_calls, after_second.count_exchanges, + after_second.immediate_payloads, after_second.completions, + after_second.point_to_point_calls, after_third.count_exchanges, + after_third.immediate_payloads, after_third.completions, + after_third.point_to_point_calls); + require_common(first_post_did_not_apply && second_completed_first && + third_preserved_order && trace_is_exact && protocol_is_exact); +} + +TEST_CASE("public ghost scheduler completes multi-node rounds and finish", + "[unit][mpi][ghost-label][scheduler][balance]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size == 1) { + return; + } + + constexpr auto nodes_per_rank = NodeID{4}; + parallel_graph_access::set_comm_rounds(8); + parallel_graph_access graph{MPI_COMM_WORLD}; + build_graph(graph, multi_node_ring_fixture(size, nodes_per_rank), rank, + NodeID{0}, true); + auto config = parhip::PPartitionConfig{}; + config.k = 1; + config.total_num_labels = 1024; + graph.init_balance_management(config); + + auto protocol = ghost_label_probe::counters{}; + auto labels_are_final = false; + auto balances_are_final = false; + auto trace_is_exact = false; + auto trace_text = std::string{}; + { + auto probe = ghost_label_probe::activation{}; + auto trace = trace_activation{}; + for (NodeID local = 0; local < nodes_per_rank; ++local) { + graph.setNodeLabel(local, NodeID{100} + graph.getGlobalID(local)); + graph.update_ghost_node_data(); + } + graph.update_ghost_node_data_finish(); + protocol = ghost_label_probe::observed; + labels_are_final = labels_are_exact(graph, NodeID{100}); + balances_are_final = ghost_balances_match_public_scheduler(graph); + auto const records = parhip::mpi::trace::snapshot(); + trace_text = parhip::mpi::trace::canonical_text(records); + trace_is_exact = trace_matches_public_scheduler(records, graph, rank); + } + + auto const expected_records = + static_cast(nodes_per_rank) * + static_cast(graph.getNumberOfAdjacentPEs()); + auto const protocol_is_exact = + protocol.topology_creations == 1 && protocol.count_exchanges == 8 && + protocol.blocking_payloads == 0 && protocol.immediate_payloads == 8 && + protocol.completions == 8 && protocol.point_to_point_calls == 0 && + protocol.barriers == 0 && + protocol.immediate_records == expected_records && + !protocol.callback_error; + CAPTURE(labels_are_final, balances_are_final, trace_is_exact, + protocol_is_exact, protocol.topology_creations, + protocol.count_exchanges, protocol.blocking_payloads, + protocol.immediate_payloads, protocol.completions, + protocol.point_to_point_calls, protocol.barriers, + protocol.immediate_records, expected_records, protocol.callback_error, + trace_text); + require_common(labels_are_final && balances_are_final && trace_is_exact && + protocol_is_exact); +} + +TEST_CASE("global ghost exchange preserves queued incremental updates", + "[unit][mpi][ghost-label][pipeline][global]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size == 1) { + return; + } + + parallel_graph_access::set_comm_rounds(8); + parallel_graph_access graph{MPI_COMM_WORLD}; + build_graph(graph, ring_fixture(size), rank, NodeID{0}, true); + auto config = parhip::PPartitionConfig{}; + config.k = 1; + config.total_num_labels = 1024; + graph.init_balance_management(config); + + auto protocol = ghost_label_probe::counters{}; + auto labels_are_final = false; + auto trace_is_exact = false; + { + auto probe = ghost_label_probe::activation{}; + auto trace = trace_activation{}; + graph.setNodeLabel(0, static_cast(100 + rank)); + graph.update_ghost_node_data_global(); + graph.setNodeLabel(0, static_cast(200 + rank)); + graph.update_ghost_node_data(false); + graph.update_ghost_node_data(false); + graph.update_ghost_node_data_finish(); + protocol = ghost_label_probe::observed; + labels_are_final = labels_are_exact(graph, NodeID{200}); + trace_is_exact = trace_preserves_queue_across_global( + parhip::mpi::trace::snapshot(), rank, size); + } + + auto const expected_incremental_records = + std::uint64_t{2} * + static_cast(graph.getNumberOfAdjacentPEs()); + auto const queue_was_preserved = + !protocol.callback_error && + protocol.immediate_records == expected_incremental_records; + CAPTURE(labels_are_final, trace_is_exact, queue_was_preserved, + protocol.immediate_records, expected_incremental_records, + protocol.callback_error); + require_common(labels_are_final && trace_is_exact && queue_was_preserved); +} + +TEST_CASE("unknown ghost IDs fail collectively without mutation and retry", + "[unit][mpi][ghost-label][transaction]") { + exercise_corruption(ghost_label_probe::corruption::unknown_id); +} + +TEST_CASE("wrong-source ghost IDs fail collectively without mutation and retry", + "[unit][mpi][ghost-label][transaction][source]") { + exercise_corruption(ghost_label_probe::corruption::wrong_source); +} diff --git a/parallel/parallel_src/tests/distributed_consistency/ghost_label_failure_probe.cpp b/parallel/parallel_src/tests/distributed_consistency/ghost_label_failure_probe.cpp new file mode 100644 index 00000000..29c9c50f --- /dev/null +++ b/parallel/parallel_src/tests/distributed_consistency/ghost_label_failure_probe.cpp @@ -0,0 +1,359 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "communication/ghost_label_update.h" +#include "communication/mpi_error.h" +#include "data_structure/parallel_graph_access.h" +#include "kahip_mpi_capabilities.h" +#include "partition_config.h" + +// KAHIP_PMPI_CALLBACK_REGION_BEGIN +namespace ghost_failure_probe { +enum class mode : std::uint8_t { + active_incremental_then_global, + skewed_incremental_protocol, + corrupted_incremental_completion, +}; + +inline mode selected = mode::active_incremental_then_global; +inline int world_rank = -1; +inline int count_exchanges = 0; +inline int blocking_payloads = 0; +inline int immediate_payloads = 0; +inline int completions = 0; +inline bool callback_error = false; +inline bool corruption_fired = false; +inline MPI_Request tracked_request = MPI_REQUEST_NULL; +inline parhip::ghost_label_update* tracked_record = nullptr; + +template +void track_incremental_payload(void* receive_buffer, + Count const receive_counts[], + Displacement const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Request const* request) noexcept { + if (selected != mode::corrupted_incremental_completion || world_rank != 0) { + return; + } + MPI_Aint lower_bound = -1; + MPI_Aint extent = -1; + if (receive_buffer == nullptr || receive_counts == nullptr || + receive_displacements == nullptr || request == nullptr || + *request == MPI_REQUEST_NULL || receive_counts[0] <= 0 || + receive_displacements[0] < 0 || + PMPI_Type_get_extent(receive_datatype, &lower_bound, &extent) != + MPI_SUCCESS || + lower_bound != 0 || + extent != static_cast(sizeof(parhip::ghost_label_update)) || + !std::in_range(receive_displacements[0])) { + callback_error = true; + return; + } + tracked_request = *request; + tracked_record = static_cast(receive_buffer) + + static_cast(receive_displacements[0]); +} + +void corrupt_completed_incremental_payload() noexcept { + if (selected != mode::corrupted_incremental_completion || world_rank != 0) { + return; + } + if (tracked_record == nullptr) { + callback_error = true; + return; + } + tracked_record->global_id = std::numeric_limits::max(); + corruption_fired = true; +} + +[[nodiscard]] auto expected_abort_state() noexcept -> bool { + if (callback_error) { + return false; + } + switch (selected) { + case mode::active_incremental_then_global: + return count_exchanges == 1 && blocking_payloads == 0 && + immediate_payloads == 1 && completions == 0; + case mode::skewed_incremental_protocol: + return count_exchanges == 0 && blocking_payloads == 0 && + immediate_payloads == 0 && completions == 0; + case mode::corrupted_incremental_completion: + return count_exchanges == 1 && blocking_payloads == 0 && + immediate_payloads == 1 && completions == 1 && + (world_rank != 0 || corruption_fired); + } + return false; +} + +void write_text(std::string_view text) noexcept { + static_cast(::write(STDERR_FILENO, text.data(), text.size())); +} + +[[noreturn]] void observed_abort() noexcept { + if (!expected_abort_state()) { + write_text( + "observed ghost-label MPI_Abort with unexpected callback state\n"); + std::_Exit(91); + } + switch (selected) { + case mode::active_incremental_then_global: + write_text( + "observed active-incremental/global MPI_Abort before blocking " + "payload\n"); + break; + case mode::skewed_incremental_protocol: + write_text( + "observed skewed incremental-protocol MPI_Abort before first " + "payload\n"); + break; + case mode::corrupted_incremental_completion: + write_text( + "observed corrupted incremental-completion terminal MPI_Abort\n"); + break; + } + std::_Exit(86); +} +} // namespace ghost_failure_probe + +static_assert(noexcept( + ghost_failure_probe::track_incremental_payload(nullptr, + nullptr, + nullptr, + MPI_DATATYPE_NULL, + nullptr))); +static_assert(noexcept( + ghost_failure_probe::track_incremental_payload( + nullptr, + nullptr, + nullptr, + MPI_DATATYPE_NULL, + nullptr))); +static_assert( + noexcept(ghost_failure_probe::corrupt_completed_incremental_payload())); +static_assert(noexcept(ghost_failure_probe::expected_abort_state())); + +extern "C" int MPI_Neighbor_alltoall(void const* send_buffer, + int send_count, + MPI_Datatype send_datatype, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + ++ghost_failure_probe::count_exchanges; + return PMPI_Neighbor_alltoall(send_buffer, send_count, send_datatype, + receive_buffer, receive_count, receive_datatype, + communicator); +} + +extern "C" int MPI_Neighbor_alltoallv(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + ++ghost_failure_probe::blocking_payloads; + return PMPI_Neighbor_alltoallv(send_buffer, send_counts, send_displacements, + send_datatype, receive_buffer, receive_counts, + receive_displacements, receive_datatype, + communicator); +} + +#if KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C +extern "C" int MPI_Neighbor_alltoallv_c(void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + ++ghost_failure_probe::blocking_payloads; + return PMPI_Neighbor_alltoallv_c(send_buffer, send_counts, send_displacements, + send_datatype, receive_buffer, + receive_counts, receive_displacements, + receive_datatype, communicator); +} +#endif + +extern "C" int MPI_Ineighbor_alltoallv(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Request* request) { + ++ghost_failure_probe::immediate_payloads; + auto const result = PMPI_Ineighbor_alltoallv( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator, request); + if (result == MPI_SUCCESS) { + ghost_failure_probe::track_incremental_payload( + receive_buffer, receive_counts, receive_displacements, receive_datatype, + request); + } + return result; +} + +#if KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV_C +extern "C" int MPI_Ineighbor_alltoallv_c(void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Request* request) { + ++ghost_failure_probe::immediate_payloads; + auto const result = PMPI_Ineighbor_alltoallv_c( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator, request); + if (result == MPI_SUCCESS) { + ghost_failure_probe::track_incremental_payload( + receive_buffer, receive_counts, receive_displacements, receive_datatype, + request); + } + return result; +} +#endif + +extern "C" int MPI_Wait(MPI_Request* request, MPI_Status* status) { + auto const tracked = + request != nullptr && + ghost_failure_probe::tracked_request != MPI_REQUEST_NULL && + *request == ghost_failure_probe::tracked_request; + ++ghost_failure_probe::completions; + auto const result = PMPI_Wait(request, status); + if (tracked && result == MPI_SUCCESS) { + ghost_failure_probe::corrupt_completed_incremental_payload(); + } + return result; +} + +extern "C" int MPI_Abort(MPI_Comm, int) { + ghost_failure_probe::observed_abort(); +} +// KAHIP_PMPI_CALLBACK_REGION_END + +namespace { +[[nodiscard]] auto parse_mode(std::string_view value) + -> std::optional { + if (value == "active-incremental-then-global") { + return ghost_failure_probe::mode::active_incremental_then_global; + } + if (value == "skewed-incremental-protocol") { + return ghost_failure_probe::mode::skewed_incremental_protocol; + } + if (value == "corrupted-incremental-completion") { + return ghost_failure_probe::mode::corrupted_incremental_completion; + } + return std::nullopt; +} + +void build_payload_graph(parhip::parallel_graph_access& graph, + int rank, + bool skew_protocol) { + parhip::parallel_graph_access::set_comm_rounds( + static_cast(8 + (skew_protocol ? rank : 0))); + graph.start_construction(1, 1, 2, 2, true); + graph.set_range(static_cast(rank), + static_cast(rank)); + auto ranges = std::vector{0, 1, 2}; + graph.set_range_array(ranges); + auto const local = graph.new_node(); + graph.setNodeWeight(local, 1); + graph.setNodeLabel(local, static_cast(rank)); + graph.setSecondPartitionIndex(local, 0); + auto const edge = + graph.new_edge(local, static_cast(rank == 0 ? 1 : 0)); + graph.setEdgeWeight(edge, 1); + graph.finish_construction(); + + auto config = parhip::PPartitionConfig{}; + config.k = 1; + config.total_num_labels = 1024; + graph.init_balance_management(config); + graph.setNodeLabel(local, static_cast(100 + rank)); +} + +[[noreturn]] void unexpected_success(std::string_view detail) { + std::fwrite(detail.data(), sizeof(char), detail.size(), stderr); + std::fputc('\n', stderr); + std::_Exit(0); +} +} // namespace + +int main(int argc, char* argv[]) { + if (argc != 2) { + std::fputs("usage: ghost_label_failure_probe MODE\n", stderr); + return 64; + } + auto const selected = parse_mode(argv[1]); + if (!selected.has_value()) { + std::fputs("unknown ghost-label failure-probe mode\n", stderr); + return 64; + } + ghost_failure_probe::selected = *selected; + + if (MPI_Init(&argc, &argv) != MPI_SUCCESS) { + std::fputs("MPI_Init failed\n", stderr); + return 70; + } + auto size = 0; + if (PMPI_Comm_rank(MPI_COMM_WORLD, &ghost_failure_probe::world_rank) != + MPI_SUCCESS || + PMPI_Comm_size(MPI_COMM_WORLD, &size) != MPI_SUCCESS || size != 2) { + std::fputs("ghost-label failure probe requires exactly two ranks\n", + stderr); + std::_Exit(70); + } + + try { + auto* graph = new parhip::parallel_graph_access{MPI_COMM_WORLD}; + build_payload_graph( + *graph, ghost_failure_probe::world_rank, + *selected == ghost_failure_probe::mode::skewed_incremental_protocol); + switch (*selected) { + case ghost_failure_probe::mode::active_incremental_then_global: + graph->update_ghost_node_data(false); + graph->update_ghost_node_data_global(); + unexpected_success( + "active incremental then global returned without fail-fast"); + case ghost_failure_probe::mode::skewed_incremental_protocol: + graph->update_ghost_node_data(false); + unexpected_success( + "skewed incremental protocol posted its first payload"); + case ghost_failure_probe::mode::corrupted_incremental_completion: + graph->update_ghost_node_data(false); + graph->update_ghost_node_data(false); + unexpected_success( + "corrupted completed incremental payload returned normally"); + } + } catch (parhip::mpi::mpi_error const&) { + unexpected_success( + "ghost-label failure escaped as a recoverable mpi_error"); + } catch (...) { + unexpected_success("ghost-label failure escaped as a C++ exception"); + } +} diff --git a/parallel/parallel_src/tests/distributed_consistency/parallel_graph_consistency_mpi_test.cpp b/parallel/parallel_src/tests/distributed_consistency/parallel_graph_consistency_mpi_test.cpp new file mode 100644 index 00000000..0a65db2e --- /dev/null +++ b/parallel/parallel_src/tests/distributed_consistency/parallel_graph_consistency_mpi_test.cpp @@ -0,0 +1,280 @@ +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "communication/ghost_exchange_plan.h" +#include "communication/mpi_adapter.h" +#include "data_structure/parallel_graph_access.h" +#include "distributed_partitioning/distributed_consistency.h" + +namespace topology_probe { +inline bool active = false; +inline int create_calls = 0; + +void reset() noexcept { + create_calls = 0; +} + +class activation final { + public: + activation() noexcept { + reset(); + active = true; + } + ~activation() noexcept { active = false; } + + activation(activation const&) = delete; + auto operator=(activation const&) -> activation& = delete; +}; +} // namespace topology_probe + +extern "C" int MPI_Dist_graph_create(MPI_Comm communicator, + int source_count, + int const sources[], + int const degrees[], + int const destinations[], + int const weights[], + MPI_Info info, + int reorder, + MPI_Comm* graph_communicator) { + if (topology_probe::active) { + ++topology_probe::create_calls; + } + return PMPI_Dist_graph_create(communicator, source_count, sources, degrees, + destinations, weights, info, reorder, + graph_communicator); +} + +namespace { +using parhip::NodeID; +using parhip::parallel_graph_access; + +[[nodiscard]] auto ranges_for(int size) -> std::vector { + auto ranges = std::vector(static_cast(size) + 1); + std::ranges::iota(ranges, NodeID{0}); + return ranges; +} + +void require_common(bool local_condition) { + auto const local = local_condition ? 1 : 0; + auto common = 0; + REQUIRE(MPI_Allreduce(&local, &common, 1, MPI_INT, MPI_MIN, MPI_COMM_WORLD) == + MPI_SUCCESS); + REQUIRE(common == 1); +} + +void build_ring(parallel_graph_access& graph, int rank, int size) { + auto targets = std::vector{}; + if (size > 1) { + targets = {(rank + size - 1) % size, (rank + 1) % size}; + std::ranges::sort(targets); + auto const unique_end = std::ranges::unique(targets); + targets.erase(unique_end.begin(), unique_end.end()); + } + auto const remote_edges = targets.size() * 2; + graph.start_construction(1, remote_edges, static_cast(size), + static_cast(size * remote_edges), false); + graph.set_range(static_cast(rank), static_cast(rank)); + auto ranges = ranges_for(size); + graph.set_range_array(ranges); + + auto const local_node = graph.new_node(); + graph.setNodeWeight(local_node, 1); + graph.setNodeLabel(local_node, static_cast(100 + rank)); + graph.setSecondPartitionIndex(local_node, static_cast(200 + rank)); + + for (auto const target : targets) { + for (int duplicate = 0; duplicate < 2; ++duplicate) { + auto const edge = graph.new_edge(local_node, static_cast(target)); + graph.setEdgeWeight(edge, 1); + } + } + graph.finish_construction(); +} + +void build_root_only_complete_graph(parallel_graph_access& graph) { + constexpr auto node_count = NodeID{2}; + graph.start_construction(node_count, 2, node_count, 2, false); + graph.set_range(0, node_count - 1); + auto ranges = std::vector{0, node_count}; + graph.set_range_array(ranges); + for (NodeID global = 0; global < node_count; ++global) { + auto const node = graph.new_node(); + graph.setNodeWeight(node, 1); + graph.setNodeLabel(node, global); + auto const edge = graph.new_edge(node, node_count - global - 1); + graph.setEdgeWeight(edge, 1); + } + graph.finish_construction(); +} +} // namespace + +TEST_CASE("graph and ghost communication ownership cannot be shallow-copied", + "[unit][mpi][ghost-plan][ownership]") { + STATIC_REQUIRE(!std::is_copy_constructible_v); + STATIC_REQUIRE(!std::is_copy_assignable_v); + STATIC_REQUIRE(!std::is_move_constructible_v); + STATIC_REQUIRE(!std::is_move_assignable_v); + STATIC_REQUIRE( + !std::is_copy_constructible_v); + STATIC_REQUIRE(!std::is_copy_assignable_v); + STATIC_REQUIRE( + !std::is_move_constructible_v); + STATIC_REQUIRE(!std::is_move_assignable_v); +} + +TEST_CASE("distributed consistency wire records have exact MPI extent", + "[unit][mpi][ghost-plan][datatype]") { + using record = parhip::distributed_consistency::node_value; + STATIC_REQUIRE(std::is_standard_layout_v); + STATIC_REQUIRE(std::is_trivially_copyable_v); + + auto datatype = parhip::mpi::make_mpi_datatype(MPI_COMM_WORLD); + MPI_Aint lower_bound = -1; + MPI_Aint extent = -1; + REQUIRE(MPI_Type_get_extent(datatype.native_handle(), &lower_bound, + &extent) == MPI_SUCCESS); + REQUIRE(lower_bound == 0); + REQUIRE(extent == static_cast(sizeof(record))); +} + +TEST_CASE("finish construction stays local including root-only graphs", + "[unit][mpi][ghost-plan][root-only]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + + auto probe = topology_probe::activation{}; + auto local_finish_is_local = true; + if (rank == 0) { + parallel_graph_access root_graph{MPI_COMM_WORLD}; + build_root_only_complete_graph(root_graph); + local_finish_is_local = topology_probe::create_calls == 0; + } + require_common(local_finish_is_local); +} + +TEST_CASE("leading zero-work ranges do not claim the first global vertex", + "[unit][mpi][graph-range][zero-local]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + + auto const leading_zero_work = size > 1 && rank == 0; + auto const local_nodes = leading_zero_work ? NodeID{0} : NodeID{1}; + auto const first = size > 1 ? static_cast(std::max(rank - 1, 0)) + : NodeID{0}; + auto const global_nodes = + size > 1 ? static_cast(size - 1) : NodeID{1}; + + parallel_graph_access graph{MPI_COMM_WORLD}; + graph.start_construction(local_nodes, 0, global_nodes, 0, false); + graph.set_range(first, first); + auto ranges = std::vector(static_cast(size) + 1); + std::ranges::transform( + std::views::iota(0, size + 1), ranges.begin(), [size](auto boundary) { + return size > 1 ? static_cast(std::max(boundary - 1, 0)) + : static_cast(boundary); + }); + graph.set_range_array(ranges); + if (!leading_zero_work) { + auto const node = graph.new_node(); + graph.setNodeLabel(node, first); + } + graph.finish_construction(); + + REQUIRE(graph.is_local_node_from_global_id(first) == !leading_zero_work); + REQUIRE(graph.find_local_id(first).has_value() == !leading_zero_work); +} + +TEST_CASE("lazy ghost plans preserve MPI order and rebuild transactionally", + "[unit][mpi][ghost-plan][cache]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE(size >= 1); + REQUIRE(size <= 5); + + parallel_graph_access graph{MPI_COMM_WORLD}; + auto probe = topology_probe::activation{}; + build_ring(graph, rank, size); + auto local_plan_is_valid = topology_probe::create_calls == 0; + + auto const& first_plan = graph.ghost_plan(); + local_plan_is_valid = + local_plan_is_valid && topology_probe::create_calls == 1; + local_plan_is_valid = + local_plan_is_valid && + std::addressof(graph.ghost_plan()) == std::addressof(first_plan) && + topology_probe::create_calls == 1; + + auto const& topology = first_plan.topology(); + local_plan_is_valid = + local_plan_is_valid && + topology.sources().size() == topology.destinations().size() && + std::ranges::is_permutation(topology.sources(), topology.destinations()); + for (std::size_t index = 0; index < topology.destinations().size(); ++index) { + auto const locals = first_plan.outgoing_local_nodes(index); + local_plan_is_valid = local_plan_is_valid && locals.size() == 1; + if (locals.size() == 1) { + local_plan_is_valid = local_plan_is_valid && locals.front() == 0; + } + } + for (std::size_t index = 0; index < topology.sources().size(); ++index) { + auto const source = topology.sources()[index]; + auto const ghosts = first_plan.expected_ghost_nodes(index); + local_plan_is_valid = local_plan_is_valid && ghosts.size() == 1; + if (ghosts.size() == 1) { + local_plan_is_valid = + local_plan_is_valid && + ghosts.front() == static_cast(source) && + graph.find_ghost_local_id(ghosts.front(), source).has_value(); + if (size > 1) { + local_plan_is_valid = + local_plan_is_valid && + !graph.find_ghost_local_id(ghosts.front(), (source + 1) % size) + .has_value(); + } + } + } + + local_plan_is_valid = + local_plan_is_valid && + graph.find_local_id(static_cast(rank)) == NodeID{0} && + !graph.find_local_id(static_cast(size + 10)).has_value(); + auto const ghost_count = graph.number_of_ghost_nodes(); + local_plan_is_valid = + local_plan_is_valid && + !graph.find_ghost_local_id(static_cast(size + 10), rank) + .has_value() && + graph.number_of_ghost_nodes() == ghost_count; + require_common(local_plan_is_valid); + + build_ring(graph, rank, size); + auto local_rebuild_is_valid = topology_probe::create_calls == 1; + static_cast(graph.ghost_plan()); + local_rebuild_is_valid = + local_rebuild_is_valid && topology_probe::create_calls == 2; + + graph.setNodeLabel(0, static_cast(300 + rank)); + graph.reinit(); + local_rebuild_is_valid = + local_rebuild_is_valid && graph.number_of_local_nodes() == 0 && + graph.number_of_ghost_nodes() == 0 && + graph.number_of_local_edges() == 0 && + !graph.find_local_id(static_cast(rank)).has_value() && + !graph.find_ghost_local_id(static_cast(rank), rank).has_value(); + require_common(local_rebuild_is_valid); +} diff --git a/parallel/parallel_src/tests/distributed_consistency/parallel_graph_lifecycle_probe.cpp b/parallel/parallel_src/tests/distributed_consistency/parallel_graph_lifecycle_probe.cpp new file mode 100644 index 00000000..9be8cb4a --- /dev/null +++ b/parallel/parallel_src/tests/distributed_consistency/parallel_graph_lifecycle_probe.cpp @@ -0,0 +1,78 @@ +#include + +#include +#include +#include +#include +#include + +#include "data_structure/parallel_graph_access.h" + +namespace { +void build_local_graph(parhip::parallel_graph_access& graph, + int rank, + int size) { + graph.start_construction(1, 0, static_cast(size), 0, false); + graph.set_range(static_cast(rank), + static_cast(rank)); + auto ranges = std::vector(static_cast(size) + 1); + std::ranges::iota(ranges, parhip::NodeID{0}); + graph.set_range_array(ranges); + auto const node = graph.new_node(); + graph.setNodeWeight(node, 1); + graph.setNodeLabel(node, static_cast(rank)); + graph.finish_construction(); +} +} // namespace + +int main(int argc, char* argv[]) { + if (argc != 2) { + return 64; + } + auto const mode = std::string_view{argv[1]}; + auto const owns_plan = mode == "cached-plan"; + auto const active_destructor = mode == "active-destructor"; + auto const active_reset = mode == "active-reset"; + auto const cnode_size_mismatch = mode == "cnode-size-mismatch"; + if (!owns_plan && !active_destructor && !active_reset && mode != "no-plan") { + if (!cnode_size_mismatch) { + return 64; + } + } + + if (MPI_Init(&argc, &argv) != MPI_SUCCESS) { + return 70; + } + auto rank = 0; + auto size = 0; + if (MPI_Comm_rank(MPI_COMM_WORLD, &rank) != MPI_SUCCESS || + MPI_Comm_size(MPI_COMM_WORLD, &size) != MPI_SUCCESS) { + return 70; + } + + auto graph = std::make_unique(MPI_COMM_WORLD); + build_local_graph(*graph, rank, size); + if (cnode_size_mismatch) { + graph->replace_node_to_cnode(std::vector{}); + return 2; + } + if (active_destructor || active_reset) { + graph->setNodeLabel(0, static_cast(rank + 1)); + graph->update_ghost_node_data(false); + if (active_reset) { + graph->reinit(); + return 2; + } + graph.reset(); + return 2; + } + if (owns_plan) { + static_cast(graph->ghost_plan()); + } + + if (MPI_Finalize() != MPI_SUCCESS) { + return 70; + } + graph.reset(); + return owns_plan ? 2 : 0; +} diff --git a/parallel/parallel_src/tests/distributed_consistency/verify_ghost_label_failure.cmake b/parallel/parallel_src/tests/distributed_consistency/verify_ghost_label_failure.cmake new file mode 100644 index 00000000..997df3d3 --- /dev/null +++ b/parallel/parallel_src/tests/distributed_consistency/verify_ghost_label_failure.cmake @@ -0,0 +1,76 @@ +cmake_minimum_required(VERSION 4.0) + +if( + NOT DEFINED MPIEXEC_EXECUTABLE + OR NOT DEFINED MPIEXEC_NUMPROC_FLAG + OR NOT DEFINED PROBE + OR NOT DEFINED MODE + OR NOT DEFINED EXPECTED_DIAGNOSTIC + OR NOT DEFINED EXPECTED_ABORT_MARKER +) + message( + FATAL_ERROR + "MPI launcher, PROBE, MODE, EXPECTED_DIAGNOSTIC, and EXPECTED_ABORT_MARKER are required" + ) +endif() + +execute_process( + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 2 + ${MPIEXEC_PREFLAGS} + "${PROBE}" "${MODE}" + ${MPIEXEC_POSTFLAGS} + RESULT_VARIABLE probe_result + OUTPUT_VARIABLE probe_stdout + ERROR_VARIABLE probe_stderr + TIMEOUT 8 +) + +set(probe_output "${probe_stdout}\n${probe_stderr}") +if("${probe_result}" STREQUAL "0") + message( + FATAL_ERROR + "ghost-label failure probe returned success unexpectedly for ${MODE}\n${probe_output}" + ) +endif() +if("${probe_result}" MATCHES "[Tt]imeout") + message( + FATAL_ERROR + "ghost-label failure probe timed out instead of terminating for ${MODE}\n${probe_output}" + ) +endif() + +string(FIND "${probe_output}" "${EXPECTED_DIAGNOSTIC}" diagnostic_offset) +if(diagnostic_offset EQUAL -1) + message( + FATAL_ERROR + "missing exact ghost-label fail-fast diagnostic '${EXPECTED_DIAGNOSTIC}' for ${MODE}\n${probe_output}" + ) +endif() + +string(FIND "${probe_output}" "${EXPECTED_ABORT_MARKER}" abort_offset) +if(abort_offset EQUAL -1) + message( + FATAL_ERROR + "missing ghost-label abort-state marker '${EXPECTED_ABORT_MARKER}' for ${MODE}\n${probe_output}" + ) +endif() + +foreach( + forbidden_marker + IN ITEMS + "unexpected callback state" + "returned without fail-fast" + "posted its first payload" + "returned normally" + "escaped as a recoverable mpi_error" + "escaped as a C++ exception" +) + string(FIND "${probe_output}" "${forbidden_marker}" forbidden_offset) + if(NOT forbidden_offset EQUAL -1) + message( + FATAL_ERROR + "ghost-label failure used a non-terminal path '${forbidden_marker}' for ${MODE}\n${probe_output}" + ) + endif() +endforeach() diff --git a/parallel/parallel_src/tests/distributed_consistency/verify_parallel_graph_lifecycle.cmake b/parallel/parallel_src/tests/distributed_consistency/verify_parallel_graph_lifecycle.cmake new file mode 100644 index 00000000..77df7b13 --- /dev/null +++ b/parallel/parallel_src/tests/distributed_consistency/verify_parallel_graph_lifecycle.cmake @@ -0,0 +1,71 @@ +if(NOT DEFINED PROBE OR NOT DEFINED MODE) + message(FATAL_ERROR "PROBE and MODE are required") +endif() + +execute_process( + COMMAND "${PROBE}" "${MODE}" + RESULT_VARIABLE probe_result + OUTPUT_VARIABLE probe_stdout + ERROR_VARIABLE probe_stderr + TIMEOUT 5 +) + +set(probe_output "${probe_stdout}\n${probe_stderr}") +if(MODE STREQUAL "no-plan") + if(NOT "${probe_result}" STREQUAL "0") + message( + FATAL_ERROR + "graph without a plan failed after finalization\n${probe_output}" + ) + endif() +elseif(MODE STREQUAL "cached-plan") + if("${probe_result}" STREQUAL "0") + message( + FATAL_ERROR + "cached graph plan survived finalization unexpectedly" + ) + endif() + if( + NOT probe_output + MATCHES + "MPI adapter ownership outlived the active MPI runtime: parallel graph cached ghost plan destruction" + ) + message( + FATAL_ERROR + "cached graph plan missed raw-abort diagnostic\n${probe_output}" + ) + endif() +elseif(MODE STREQUAL "active-destructor" OR MODE STREQUAL "active-reset") + if("${probe_result}" STREQUAL "0") + message( + FATAL_ERROR + "active graph generation ${MODE} returned success unexpectedly" + ) + endif() + if( + NOT probe_output + MATCHES + "MPI adapter programming failure: parallel graph (destroyed with an active ghost generation|reset requires idle ghost communication)" + ) + message( + FATAL_ERROR + "active graph generation missed fail-fast diagnostic\n${probe_output}" + ) + endif() +elseif(MODE STREQUAL "cnode-size-mismatch") + if("${probe_result}" STREQUAL "0") + message(FATAL_ERROR "wrong-sized CNode replacement returned success") + endif() + if( + NOT probe_output + MATCHES + "MPI adapter programming failure: parallel graph CNode replacement size mismatch" + ) + message( + FATAL_ERROR + "wrong-sized CNode replacement missed fail-fast diagnostic\n${probe_output}" + ) + endif() +else() + message(FATAL_ERROR "unknown lifecycle probe mode: ${MODE}") +endif() diff --git a/parallel/parallel_src/tests/distributed_partitioning/distributed_partitioner_failure_probe.cpp b/parallel/parallel_src/tests/distributed_partitioning/distributed_partitioner_failure_probe.cpp new file mode 100644 index 00000000..eb87c25c --- /dev/null +++ b/parallel/parallel_src/tests/distributed_partitioning/distributed_partitioner_failure_probe.cpp @@ -0,0 +1,244 @@ +#include +#include + +#include +#include +#include +#include +#include + +#include "data_structure/parallel_graph_access.h" +#include "distributed_partitioning/distributed_partitioner.h" +#include "distributed_partitioning/initial_partitioning/random_initial_partitioning.h" +#include "partition_config.h" + +// KAHIP_PMPI_CALLBACK_REGION_BEGIN +namespace distributed_partitioner_failure_probe { +enum class mode : unsigned char { + zero_k, + mismatched_k, + zero_cluster_factor, + infinite_cluster_factor, + negative_choice_count, + choice_capacity, + exhausted_choice_cursor, + rank_backend, + mismatched_communicator, +}; + +inline mode selected = mode::zero_k; +inline bool active = false; +inline MPI_Comm affected_communicator = MPI_COMM_NULL; +inline parhip::parallel_graph_access* graph = nullptr; +inline parhip::NodeID sentinel = 99; + +void write_text(std::string_view text) noexcept { + static_cast(::write(STDERR_FILENO, text.data(), text.size())); +} + +[[nodiscard]] auto labels_are_untouched() noexcept -> bool { + if (graph == nullptr) { + return true; + } + for (parhip::NodeID node = 0; node < graph->number_of_local_nodes(); ++node) { + if (graph->getNodeLabel(node) != sentinel) { + return false; + } + } + return true; +} + +[[noreturn]] void observed_abort(MPI_Comm communicator, + int error_code) noexcept { + auto relation = int{MPI_UNEQUAL}; + if (!active || error_code != EXIT_FAILURE || + PMPI_Comm_compare(communicator, affected_communicator, &relation) != + MPI_SUCCESS || + relation != MPI_IDENT || !labels_are_untouched()) { + write_text("observed distributed-partitioner MPI_Abort with unexpected " + "state\n"); + std::_Exit(91); + } + write_text( + "observed distributed-partitioner MPI_Abort on affected communicator " + "before graph mutation\n"); + if (PMPI_Barrier(MPI_COMM_WORLD) != MPI_SUCCESS) { + std::_Exit(90); + } + std::_Exit(86); +} +} // namespace distributed_partitioner_failure_probe + +static_assert( + noexcept(distributed_partitioner_failure_probe::write_text({}))); +static_assert( + noexcept(distributed_partitioner_failure_probe::labels_are_untouched())); +static_assert(noexcept(distributed_partitioner_failure_probe::observed_abort( + MPI_COMM_NULL, 0))); + +extern "C" int MPI_Comm_rank(MPI_Comm communicator, int* rank) { + using namespace distributed_partitioner_failure_probe; + if (active && selected == mode::rank_backend && + communicator == affected_communicator) { + return MPI_ERR_OTHER; + } + return PMPI_Comm_rank(communicator, rank); +} + +extern "C" int MPI_Abort(MPI_Comm communicator, int error_code) { + distributed_partitioner_failure_probe::observed_abort(communicator, + error_code); +} +// KAHIP_PMPI_CALLBACK_REGION_END + +namespace { +using distributed_partitioner_failure_probe::mode; + +[[nodiscard]] auto parse_mode(std::string_view value) -> mode { + if (value == "zero-k") { + return mode::zero_k; + } + if (value == "mismatched-k") { + return mode::mismatched_k; + } + if (value == "zero-cluster-factor") { + return mode::zero_cluster_factor; + } + if (value == "infinite-cluster-factor") { + return mode::infinite_cluster_factor; + } + if (value == "negative-choice-count") { + return mode::negative_choice_count; + } + if (value == "choice-capacity") { + return mode::choice_capacity; + } + if (value == "exhausted-choice-cursor") { + return mode::exhausted_choice_cursor; + } + if (value == "rank-backend") { + return mode::rank_backend; + } + if (value == "mismatched-communicator") { + return mode::mismatched_communicator; + } + std::fprintf(stderr, "unknown failure mode: %.*s\n", + static_cast(value.size()), value.data()); + std::exit(64); +} + +void build_single_isolate(parhip::parallel_graph_access& graph, + MPI_Comm communicator) { + auto rank = 0; + auto size = 0; + if (PMPI_Comm_rank(communicator, &rank) != MPI_SUCCESS || + PMPI_Comm_size(communicator, &size) != MPI_SUCCESS) { + std::exit(70); + } + auto ranges = std::vector( + static_cast(size) + 1, parhip::NodeID{1}); + ranges.front() = 0; + auto const first = ranges[static_cast(rank)]; + auto const end = ranges[static_cast(rank + 1)]; + graph.start_construction(end - first, 0, 1, 0, false); + graph.set_range(first, first == end ? first : end - 1); + graph.set_range_array(ranges); + if (first != end) { + auto const local = graph.new_node(); + graph.setNodeWeight(local, 7); + graph.setNodeLabel(local, + distributed_partitioner_failure_probe::sentinel); + graph.setSecondPartitionIndex(local, 0); + } + graph.finish_construction(); +} +} // namespace + +int main(int argc, char** argv) { + using namespace distributed_partitioner_failure_probe; + if (argc != 2) { + std::fputs("usage: distributed_partitioner_failure_probe MODE\n", stderr); + return 64; + } + selected = parse_mode(argv[1]); + if (MPI_Init(&argc, &argv) != MPI_SUCCESS) { + return 70; + } + MPI_Comm_set_errhandler(MPI_COMM_WORLD, MPI_ERRORS_RETURN); + + auto world_rank = 0; + auto world_size = 0; + PMPI_Comm_rank(MPI_COMM_WORLD, &world_rank); + PMPI_Comm_size(MPI_COMM_WORLD, &world_size); + if (PMPI_Comm_split(MPI_COMM_WORLD, 0, world_size - world_rank, + &affected_communicator) != MPI_SUCCESS) { + return 70; + } + MPI_Comm_set_errhandler(affected_communicator, MPI_ERRORS_RETURN); + + auto const graph_communicator = + selected == mode::mismatched_communicator ? MPI_COMM_WORLD + : affected_communicator; + { + auto owned_graph = parhip::parallel_graph_access{graph_communicator}; + build_single_isolate(owned_graph, graph_communicator); + graph = &owned_graph; + + auto config = parhip::PPartitionConfig{}; + config.k = 2; + config.num_tries = 1; + config.num_vcycles = 0; + config.cluster_coarsening_factor = 14; + config.upper_bound_partition = 7; + if (selected == mode::zero_k) { + config.k = 0; + } else if (selected == mode::mismatched_k) { + config.k = world_rank == 0 ? 0 : 2; + } else if (selected == mode::zero_cluster_factor) { + config.cluster_coarsening_factor = 0; + } else if (selected == mode::infinite_cluster_factor) { + config.cluster_coarsening_factor = + std::numeric_limits::infinity(); + } else if (selected == mode::negative_choice_count) { + config.num_tries = -1; + config.num_vcycles = 2; + } else if (selected == mode::choice_capacity) { + config.num_tries = std::numeric_limits::max(); + config.num_vcycles = std::numeric_limits::max(); + } else if (selected == mode::exhausted_choice_cursor) { + config.num_tries = 0; + config.num_vcycles = 1; + } + + active = true; + if (selected == mode::zero_k || selected == mode::mismatched_k || + selected == mode::mismatched_communicator) { + auto random = parhip::random_initial_partitioning{}; + random.perform_partitioning( + parhip::mpi::communicator_view{affected_communicator}, config, + owned_graph); + } else if (selected == mode::negative_choice_count || + selected == mode::choice_capacity) { + parhip::distributed_partitioner::generate_random_choices( + config, parhip::mpi::communicator_view{affected_communicator}); + } else if (selected == mode::exhausted_choice_cursor) { + parhip::distributed_partitioner::generate_random_choices( + config, parhip::mpi::communicator_view{affected_communicator}); + config.eco = true; + auto partitioner = parhip::distributed_partitioner{}; + partitioner.perform_partitioning(affected_communicator, config, + owned_graph); + } else { + auto partitioner = parhip::distributed_partitioner{}; + partitioner.perform_partitioning(affected_communicator, config, + owned_graph); + } + active = false; + graph = nullptr; + } + + PMPI_Comm_free(&affected_communicator); + MPI_Finalize(); + std::fputs("failure mode returned without fail-fast termination\n", stderr); + return 72; +} diff --git a/parallel/parallel_src/tests/distributed_partitioning/partition_config_test.cpp b/parallel/parallel_src/tests/distributed_partitioning/partition_config_test.cpp new file mode 100644 index 00000000..2158ba78 --- /dev/null +++ b/parallel/parallel_src/tests/distributed_partitioning/partition_config_test.cpp @@ -0,0 +1,61 @@ +#include + +#include +#include + +#include "partition_config.h" + +TEST_CASE("PPartitionConfig default state is deterministic and neutral") { + auto const config = parhip::PPartitionConfig{}; + + auto const neutral_scalars = std::tuple{ + config.log_num_verts, + config.edge_factor, + config.generate_rgg, + config.generate_ba, + config.comm_rounds, + config.number_of_overall_nodes, + std::to_underlying(config.permutation_quality), + config.label_iterations, + config.label_iterations_coarsening, + config.label_iterations_refinement, + config.cluster_coarsening_factor, + config.time_limit, + config.epsilon, + config.inbalance, + config.seed, + config.k, + config.evolutionary_time_limit, + config.upper_bound_partition, + config.upper_bound_cluster, + config.total_num_labels, + std::to_underlying(config.initial_partitioning_algorithm), + config.stop_factor, + config.vcycle, + config.num_vcycles, + config.num_tries, + std::to_underlying(config.node_ordering), + config.no_refinement_in_last_iteration, + config.ht_fill_factor, + config.eco, + config.binary_io_window_size, + config.barabasi_albert_mindegree, + config.compute_degree_sequence_ba, + config.compute_degree_sequence_k_first, + config.kronecker_internal_only, + config.k_deg, + config.generate_ba_32bit, + config.n, + config.save_partition, + config.save_partition_binary, + config.vertex_degree_weights, + config.converter_evaluate, + }; + + CHECK(std::apply( + [](auto const... value) { return ((value == 0) && ...); }, + neutral_scalars)); + CHECK(config.input_partition.empty()); + CHECK(config.graph_filename.empty()); + CHECK(config.input_partition_filename.empty()); +} diff --git a/parallel/parallel_src/tests/distributed_partitioning/random_initial_partitioning_mpi_test.cpp b/parallel/parallel_src/tests/distributed_partitioning/random_initial_partitioning_mpi_test.cpp new file mode 100644 index 00000000..37453963 --- /dev/null +++ b/parallel/parallel_src/tests/distributed_partitioning/random_initial_partitioning_mpi_test.cpp @@ -0,0 +1,162 @@ +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "data_structure/parallel_graph_access.h" +#include "communication/mpi_handles.h" +#include "distributed_partitioning/distributed_partitioner.h" +#include "distributed_partitioning/initial_partitioning/random_initial_partitioning.h" +#include "partition_config.h" +#include "tools/random_functions.h" + +namespace { +using parhip::NodeID; +using parhip::NodeWeight; +using parhip::PPartitionConfig; +using parhip::parallel_graph_access; + +class scoped_communicator final { + public: + explicit scoped_communicator(MPI_Comm communicator) noexcept + : communicator_(communicator) {} + + ~scoped_communicator() { + if (communicator_ != MPI_COMM_NULL) { + REQUIRE(MPI_Comm_free(&communicator_) == MPI_SUCCESS); + } + } + + scoped_communicator(scoped_communicator const&) = delete; + auto operator=(scoped_communicator const&) -> scoped_communicator& = delete; + + [[nodiscard]] auto get() const noexcept -> MPI_Comm { return communicator_; } + + private: + MPI_Comm communicator_ = MPI_COMM_NULL; +}; + +[[nodiscard]] auto reversed_world() -> scoped_communicator { + auto world_rank = 0; + auto world_size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &world_rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &world_size) == MPI_SUCCESS); + auto result = MPI_COMM_NULL; + REQUIRE(MPI_Comm_split(MPI_COMM_WORLD, 0, world_size - world_rank, &result) == + MPI_SUCCESS); + return scoped_communicator{result}; +} + +void build_weighted_isolates(parallel_graph_access& graph, + MPI_Comm communicator) { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(communicator, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(communicator, &size) == MPI_SUCCESS); + + constexpr auto global_nodes = NodeID{4}; + auto ranges = std::vector(static_cast(size) + 1, + global_nodes); + ranges.front() = 0; + auto const first = ranges[static_cast(rank)]; + auto const end = ranges[static_cast(rank + 1)]; + graph.start_construction(end - first, 0, global_nodes, 0, false); + graph.set_range(first, first == end ? first : end - 1); + graph.set_range_array(ranges); + + constexpr auto weights = std::array{2, 3, 5, 7}; + for (auto global = first; global < end; ++global) { + auto const local = graph.new_node(); + graph.setNodeWeight(local, weights[static_cast(global)]); + graph.setNodeLabel(local, NodeID{99}); + graph.setSecondPartitionIndex(local, 0); + } + graph.finish_construction(); +} + +[[nodiscard]] auto local_labels(parallel_graph_access& graph) + -> std::vector { + auto result = std::vector(graph.number_of_local_nodes()); + std::ranges::transform( + std::views::iota(NodeID{0}, graph.number_of_local_nodes()), + result.begin(), [&](NodeID node) { return graph.getNodeLabel(node); }); + return result; +} + +[[nodiscard]] auto global_block_weights(parallel_graph_access& graph, + MPI_Comm communicator) + -> std::array { + auto local = std::array{}; + for (NodeID node = 0; node < graph.number_of_local_nodes(); ++node) { + auto const block = graph.getNodeLabel(node); + REQUIRE(block < local.size()); + local[static_cast(block)] += graph.getNodeWeight(node); + } + auto global = std::array{}; + REQUIRE(MPI_Allreduce(local.data(), global.data(), + static_cast(global.size()), + MPI_UNSIGNED_LONG_LONG, MPI_SUM, communicator) == + MPI_SUCCESS); + return global; +} +} // namespace + +TEST_CASE("random initial partitioning preserves deterministic weighted labels " + "with zero-local-work ranks", + "[unit][mpi][distributed-partitioner][random]") { + auto communicator = reversed_world(); + auto graph = parallel_graph_access{communicator.get()}; + build_weighted_isolates(graph, communicator.get()); + + auto config = PPartitionConfig{}; + config.k = 3; + auto partitioner = parhip::random_initial_partitioning{}; + + for (auto repetition = 0; repetition < 2; ++repetition) { + parhip::random_functions::setSeed(23); + partitioner.perform_partitioning( + parhip::mpi::communicator_view{communicator.get()}, config, graph); + + auto rank = 0; + REQUIRE(MPI_Comm_rank(communicator.get(), &rank) == MPI_SUCCESS); + if (rank == 0) { + REQUIRE(local_labels(graph) == std::vector{1, 2, 2, 0}); + } else { + REQUIRE(local_labels(graph).empty()); + } + REQUIRE(global_block_weights(graph, communicator.get()) == + std::array{7, 2, 8}); + } +} + +TEST_CASE("random-choice generation preserves the upstream draw stream across " + "repeated calls", + "[unit][mpi][distributed-partitioner][random]") { + auto config = PPartitionConfig{}; + config.num_tries = 2; + config.num_vcycles = 3; + + parhip::random_functions::setSeed(17); + parhip::distributed_partitioner::generate_random_choices( + config, parhip::mpi::communicator_view{MPI_COMM_WORLD}); + REQUIRE(parhip::random_functions::nextInt(0ULL, 1000000ULL) == 637521); + REQUIRE(std::bit_cast( + parhip::random_functions::nextDouble(0.0, 1.0)) == + 4592438611616939308ULL); + + parhip::random_functions::setSeed(17); + parhip::distributed_partitioner::generate_random_choices( + config, parhip::mpi::communicator_view{MPI_COMM_WORLD}); + parhip::distributed_partitioner::generate_random_choices( + config, parhip::mpi::communicator_view{MPI_COMM_WORLD}); + REQUIRE(parhip::random_functions::nextInt(0ULL, 1000000ULL) == 864042); + REQUIRE(std::bit_cast( + parhip::random_functions::nextDouble(0.0, 1.0)) == + 4598724536360494900ULL); +} diff --git a/parallel/parallel_src/tests/distributed_partitioning/serial_kernel_profile_mpi_test.cpp b/parallel/parallel_src/tests/distributed_partitioning/serial_kernel_profile_mpi_test.cpp new file mode 100644 index 00000000..696b3d2c --- /dev/null +++ b/parallel/parallel_src/tests/distributed_partitioning/serial_kernel_profile_mpi_test.cpp @@ -0,0 +1,168 @@ +#include + +#include +#include +#include + +#include + +#include "communication/mpi_tools.h" +#include "communication/serial_kernel_profile_observer.h" +#include "data_structure/parallel_graph_access.h" +#include "distributed_partitioning/initial_partitioning/distributed_evolutionary_partitioning.h" +#include "partition_config.h" +#include "tools/random_functions.h" + +namespace { +void build_zero_work_quotient(parhip::parallel_graph_access& graph, + MPI_Comm communicator) { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(communicator, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(communicator, &size) == MPI_SUCCESS); + auto ranges = std::vector(static_cast(size) + 1, + parhip::NodeID{2}); + ranges.front() = 0; + auto const first = ranges[static_cast(rank)]; + auto const end = ranges[static_cast(rank + 1)]; + graph.start_construction(end - first, 0, 2, 0, false); + graph.set_range(first, first == end ? first : end - 1); + graph.set_range_array(ranges); + for (auto global = first; global < end; ++global) { + auto const node = graph.new_node(); + graph.setNodeWeight(node, 1); + graph.setSecondPartitionIndex(node, 0); + } + graph.finish_construction(); +} + +[[nodiscard]] auto fields( + kahip::serial_kernel::serial_kernel_profile const& profile) + -> std::array { + return {profile.global_nodes, + profile.global_directed_edges, + profile.total_node_weight, + profile.maximum_node_weight, + profile.total_directed_edge_weight, + profile.maximum_directed_edge_weight, + profile.block_count, + profile.absolute_bound, + profile.wire_record_bytes, + profile.csr_bytes, + profile.partition_bytes, + profile.serial_input_bytes, + profile.complete_graph_bytes, + profile.structural_validation_bytes, + profile.base_memory_bytes, + profile.flat_payload_elements, + static_cast(profile.reason)}; +} + +struct profile_capture final { + std::array profiles{}; + std::size_t count{}; +}; + +void capture( + void* context, + kahip::serial_kernel::serial_kernel_profile const& profile) noexcept { + auto& destination = *static_cast(context); + if (destination.count < destination.profiles.size()) { + destination.profiles[destination.count++] = profile; + } +} +} // namespace + +TEST_CASE("serial-kernel profile agrees exactly with zero-local-work ranks", + "[mpi][serial-kernel][profile]") { + auto graph = parhip::parallel_graph_access{MPI_COMM_WORLD}; + build_zero_work_quotient(graph, MPI_COMM_WORLD); + auto config = parhip::PPartitionConfig{}; + config.k = 1; + config.upper_bound_partition = 2; + + auto const profile = parhip::mpi_tools{}.preflight_serial_kernel( + MPI_COMM_WORLD, config, graph); + REQUIRE(profile.safe()); + REQUIRE(profile.global_nodes == 2); + REQUIRE(profile.global_directed_edges == 0); + + auto local = fields(profile); + REQUIRE(local == std::array{ + 2, 0, 2, 1, 0, 0, 1, 2, 64, 20, 8, 28, 120, 0, + 184, 5, + static_cast( + kahip::serial_kernel::profile_reason::none)}); + auto all = std::array{}; + REQUIRE(MPI_Allreduce(local.data(), all.data(), + static_cast(local.size()), MPI_UINT64_T, MPI_MIN, + MPI_COMM_WORLD) == MPI_SUCCESS); + REQUIRE(all == local); +} + +TEST_CASE("single-block distributed bridge never enters the generic kernel", + "[mpi][serial-kernel][bridge]") { + auto graph = parhip::parallel_graph_access{MPI_COMM_WORLD}; + build_zero_work_quotient(graph, MPI_COMM_WORLD); + auto config = parhip::PPartitionConfig{}; + config.k = 1; + config.upper_bound_partition = 2; + config.seed = 73; + parhip::random_functions::setSeed(config.seed); + auto const expected_next = parhip::random_functions::nextInt(0, 1'000'000); + parhip::random_functions::setSeed(config.seed); + parhip::distributed_evolutionary_partitioning{}.perform_partitioning( + MPI_COMM_WORLD, config, graph); + for (parhip::NodeID node = 0; node < graph.number_of_local_nodes(); ++node) { + CHECK(graph.getNodeLabel(node) == 0); + } + CHECK(parhip::random_functions::nextInt(0, 1'000'000) == expected_next); +} + +TEST_CASE("private serial profile observer captures only checked gather profiles", + "[mpi][serial-kernel][profile][observer]") { + auto graph = parhip::parallel_graph_access{MPI_COMM_WORLD}; + build_zero_work_quotient(graph, MPI_COMM_WORLD); + auto config = parhip::PPartitionConfig{}; + config.k = 1; + config.upper_bound_partition = 2; + config.initial_partitioning_algorithm = + parhip::InitialPartitioningAlgorithm::KAFFPAEFASTSNW; + auto const expected = std::array{ + 2, 0, 2, 1, 0, 0, 1, 2, 64, 20, 8, 28, 120, 0, 184, 5, + static_cast( + kahip::serial_kernel::profile_reason::none)}; + + auto outer_capture = profile_capture{}; + auto inner_capture = profile_capture{}; + parhip::distributed_evolutionary_partitioning{}.perform_partitioning( + MPI_COMM_WORLD, config, graph); + CHECK(outer_capture.count == 0); + CHECK(inner_capture.count == 0); + + { + auto outer = parhip::mpi_tools_detail::scoped_serial_kernel_profile_observer{ + capture, &outer_capture}; + parhip::distributed_evolutionary_partitioning{}.perform_partitioning( + MPI_COMM_WORLD, config, graph); + REQUIRE(outer_capture.count == 1); + CHECK(fields(outer_capture.profiles[0]) == expected); + { + auto inner = + parhip::mpi_tools_detail::scoped_serial_kernel_profile_observer{ + capture, &inner_capture}; + parhip::distributed_evolutionary_partitioning{}.perform_partitioning( + MPI_COMM_WORLD, config, graph); + CHECK(outer_capture.count == 1); + REQUIRE(inner_capture.count == 1); + CHECK(fields(inner_capture.profiles[0]) == expected); + } + parhip::distributed_evolutionary_partitioning{}.perform_partitioning( + MPI_COMM_WORLD, config, graph); + CHECK(outer_capture.count == 2); + } + parhip::distributed_evolutionary_partitioning{}.perform_partitioning( + MPI_COMM_WORLD, config, graph); + CHECK(outer_capture.count == 2); + CHECK(inner_capture.count == 1); +} diff --git a/parallel/parallel_src/tests/distributed_partitioning/serial_kernel_profile_test.cpp b/parallel/parallel_src/tests/distributed_partitioning/serial_kernel_profile_test.cpp new file mode 100644 index 00000000..79a90a7c --- /dev/null +++ b/parallel/parallel_src/tests/distributed_partitioning/serial_kernel_profile_test.cpp @@ -0,0 +1,554 @@ +#include +#include +#include +#include + +#include + +#include "serial_kernel_profile.h" +#include "serial_kernel_bridge.h" +#include "serial_kernel_structure.h" +#include "range_owner.h" + +namespace { +using kahip::serial_kernel::profile_input; +using kahip::serial_kernel::profile_limits; +using kahip::serial_kernel::profile_reason; + +TEST_CASE("serial-kernel profile accounts for a safe weighted quotient", + "[serial-kernel][profile]") { + auto const input = profile_input{ + .global_nodes = 3, + .global_directed_edges = 4, + .total_node_weight = 9, + .maximum_node_weight = 5, + .total_directed_edge_weight = 12, + .maximum_directed_edge_weight = 6, + .block_count = 2, + .absolute_bound = 5, + .labels_are_valid = true, + }; + + auto const profile = kahip::serial_kernel::make_profile(input); + + CHECK(profile.reason == profile_reason::none); + CHECK(profile.wire_record_bytes == 160); + CHECK(profile.csr_bytes == 60); + CHECK(profile.partition_bytes == 12); + CHECK(profile.serial_input_bytes == 72); + CHECK(profile.complete_graph_bytes == 192); + CHECK(profile.structural_validation_bytes == 96); + CHECK(profile.base_memory_bytes == 448); + CHECK(profile.flat_payload_elements == 15); +} + +TEST_CASE("serial-kernel profile rejects each narrowed scalar one past its domain", + "[serial-kernel][profile]") { + auto input = profile_input{ + .global_nodes = 1, + .global_directed_edges = 0, + .total_node_weight = 1, + .maximum_node_weight = 1, + .total_directed_edge_weight = 0, + .maximum_directed_edge_weight = 0, + .block_count = 1, + .absolute_bound = 1, + .labels_are_valid = true, + }; + + input.global_nodes = static_cast( + std::numeric_limits::max()) + 1; + CHECK(kahip::serial_kernel::make_profile(input).reason == + profile_reason::global_node_count_out_of_range); + + input.global_nodes = 1; + input.global_directed_edges = static_cast( + std::numeric_limits::max()) + 1; + CHECK(kahip::serial_kernel::make_profile(input).reason == + profile_reason::global_directed_edge_count_out_of_range); + + input.global_directed_edges = 0; + input.maximum_node_weight = static_cast( + std::numeric_limits::max()) + 1; + CHECK(kahip::serial_kernel::make_profile(input).reason == + profile_reason::node_weight_out_of_range); + + input.maximum_node_weight = 1; + input.maximum_directed_edge_weight = static_cast( + std::numeric_limits::max()) + 1; + CHECK(kahip::serial_kernel::make_profile(input).reason == + profile_reason::edge_weight_out_of_range); + + input.maximum_directed_edge_weight = 0; + input.total_node_weight = static_cast( + std::numeric_limits::max()) + 1; + CHECK(kahip::serial_kernel::make_profile(input).reason == + profile_reason::total_node_weight_out_of_range); + + input.total_node_weight = 1; + input.absolute_bound = static_cast( + std::numeric_limits::max()) + 1; + CHECK(kahip::serial_kernel::make_profile(input).reason == + profile_reason::absolute_bound_out_of_range); + + input.absolute_bound = 1; + input.total_directed_edge_weight = static_cast( + std::numeric_limits::max()) + 1; + CHECK(kahip::serial_kernel::make_profile(input).reason == + profile_reason::total_directed_edge_weight_out_of_range); +} + +TEST_CASE("serial-kernel profile accepts exact scalar limits", + "[serial-kernel][profile]") { + auto node_limit = profile_input{ + .global_nodes = 1, + .total_node_weight = std::numeric_limits::max(), + .maximum_node_weight = std::numeric_limits::max(), + .block_count = 1, + .absolute_bound = std::numeric_limits::max() / 2, + }; + CHECK(kahip::serial_kernel::make_profile(node_limit).safe()); + + auto edge_limit = profile_input{ + .global_nodes = 1, + .global_directed_edges = std::numeric_limits::max(), + .total_node_weight = 1, + .maximum_node_weight = 1, + .total_directed_edge_weight = std::numeric_limits::max(), + .maximum_directed_edge_weight = std::numeric_limits::max(), + .block_count = 1, + .absolute_bound = 1, + .bank_factor_twice = 1, + }; + CHECK(kahip::serial_kernel::make_profile(edge_limit).safe()); +} + +TEST_CASE("serial-kernel profile honors modified-kernel aggregate domains", + "[serial-kernel][profile]") { + auto input = profile_input{ + .global_nodes = 2, + .global_directed_edges = 0, + .total_node_weight = 2, + .maximum_node_weight = 1, + .total_directed_edge_weight = 0, + .maximum_directed_edge_weight = 0, + .block_count = 2, + .absolute_bound = 1, + .labels_are_valid = true, + }; + + input.total_node_weight = static_cast( + std::numeric_limits::max()) + 1; + CHECK(kahip::serial_kernel::make_profile(input).reason == + profile_reason::total_node_weight_out_of_range); + + input.total_node_weight = 2; + input.absolute_bound = + static_cast(std::numeric_limits::max() / 2) + 1; + CHECK(kahip::serial_kernel::make_profile(input).reason == + profile_reason::absolute_bound_out_of_range); + + input.absolute_bound = 1; + input.block_count = 0; + CHECK(kahip::serial_kernel::make_profile(input).reason == + profile_reason::block_count_out_of_range); + + input.block_count = 3; + CHECK(kahip::serial_kernel::make_profile(input).reason == + profile_reason::block_count_out_of_range); + + input.global_nodes = std::numeric_limits::max(); + input.block_count = static_cast( + std::numeric_limits::max()) / 60 + 1; + CHECK(kahip::serial_kernel::make_profile(input).reason == + profile_reason::stop_rule_domain_out_of_range); + + input.block_count = 2; + input.global_nodes = 2; + input.global_directed_edges = std::numeric_limits::max() - 1; + input.bank_factor_twice = 6; + CHECK(kahip::serial_kernel::make_profile(input).safe()); +} + +TEST_CASE("serial-kernel profile bounds the quotient scheduler exactly", + "[serial-kernel][profile]") { + auto input = profile_input{ + .global_nodes = 4, + .global_directed_edges = 12, + .total_node_weight = 4, + .maximum_node_weight = 1, + .block_count = 4, + .absolute_bound = 1, + .bank_factor_twice = 6, + }; + auto limits = profile_limits::native(); + limits.int_max = 18; + CHECK(kahip::serial_kernel::make_profile(input, limits).safe()); + + limits.int_max = 17; + CHECK(kahip::serial_kernel::make_profile(input, limits).reason == + profile_reason::quotient_scheduler_domain_out_of_range); +} + +TEST_CASE("serial-kernel profile rejects bad v-cycle labels", + "[serial-kernel][profile]") { + auto const input = profile_input{ + .global_nodes = 2, + .global_directed_edges = 2, + .total_node_weight = 2, + .maximum_node_weight = 1, + .total_directed_edge_weight = 2, + .maximum_directed_edge_weight = 1, + .block_count = 2, + .absolute_bound = 1, + .labels_are_valid = false, + }; + CHECK(kahip::serial_kernel::make_profile(input).reason == + profile_reason::vcycle_labels_out_of_range); +} + +TEST_CASE("serial-kernel profile rejects vectors beyond their capacity", + "[serial-kernel][profile]") { + auto const input = profile_input{ + .global_nodes = 2, + .global_directed_edges = 2, + .total_node_weight = 2, + .maximum_node_weight = 1, + .total_directed_edge_weight = 2, + .maximum_directed_edge_weight = 1, + .block_count = 2, + .absolute_bound = 1, + .labels_are_valid = true, + }; + auto limits = profile_limits::native(); + limits.xadj_elements = 2; + CHECK(kahip::serial_kernel::make_profile(input, limits).reason == + profile_reason::vector_capacity_exceeded); +} + +TEST_CASE("serial-kernel profile accounts for both complete-graph sentinels", + "[serial-kernel][profile]") { + auto const input = profile_input{ + .global_nodes = 2, + .global_directed_edges = 0, + .total_node_weight = 2, + .maximum_node_weight = 1, + .block_count = 1, + .absolute_bound = 2, + }; + auto limits = profile_limits::native(); + limits.complete_node_elements = 3; + limits.complete_node_data_elements = 3; + CHECK(kahip::serial_kernel::make_profile(input, limits).safe()); + + limits.complete_node_data_elements = 2; + CHECK(kahip::serial_kernel::make_profile(input, limits).reason == + profile_reason::vector_capacity_exceeded); +} + +TEST_CASE("serial-kernel profile checks the combined distribution payload", + "[serial-kernel][profile]") { + auto const input = profile_input{ + .global_nodes = 2, + .global_directed_edges = 3, + .total_node_weight = 2, + .maximum_node_weight = 1, + .total_directed_edge_weight = 3, + .maximum_directed_edge_weight = 1, + .block_count = 1, + .absolute_bound = 2, + }; + auto limits = profile_limits::native(); + // Each array fits independently: xadj=3, adjncy=3, node=2, edge=3. + limits.xadj_elements = 3; + limits.adjncy_elements = 3; + limits.node_weight_elements = 2; + limits.edge_weight_elements = 3; + limits.partition_elements = 2; + limits.flat_payload_elements = 11; + auto const profile = kahip::serial_kernel::make_profile(input, limits); + CHECK(profile.flat_payload_elements == 11); + CHECK(profile.safe()); + + limits.flat_payload_elements = 10; + CHECK(kahip::serial_kernel::make_profile(input, limits).reason == + profile_reason::vector_capacity_exceeded); +} + +TEST_CASE("serial-kernel profile detects checked byte arithmetic overflow", + "[serial-kernel][profile]") { + auto const input = profile_input{ + .global_nodes = 2, + .global_directed_edges = 0, + .total_node_weight = 0, + .maximum_node_weight = 0, + .total_directed_edge_weight = 0, + .maximum_directed_edge_weight = 0, + .block_count = 2, + .absolute_bound = 0, + .labels_are_valid = true, + }; + auto limits = profile_limits::native(); + limits.size_limit = 7; + CHECK(kahip::serial_kernel::make_profile(input, limits).reason == + profile_reason::byte_count_overflow); +} + +TEST_CASE("serial-kernel profile accounts every byte stage at its boundary", + "[serial-kernel][profile]") { + auto const input = profile_input{ + .global_nodes = 3, + .global_directed_edges = 4, + .total_node_weight = 3, + .maximum_node_weight = 1, + .total_directed_edge_weight = 4, + .maximum_directed_edge_weight = 1, + .block_count = 2, + .absolute_bound = 2, + }; + using kahip::serial_kernel::byte_accounting_stage; + auto const accounting = kahip::serial_kernel::account_profile_bytes(input); + REQUIRE(accounting.safe()); + struct byte_total_case final { + std::uint64_t kahip::serial_kernel::profile_byte_accounting::*member; + std::uint64_t profile_limits::*limit; + byte_accounting_stage rejection_stage; + std::uint64_t exact; + }; + auto const totals = std::array{ + byte_total_case{&kahip::serial_kernel::profile_byte_accounting::node_wire_bytes, + &profile_limits::node_wire_byte_limit, + byte_accounting_stage::node_wire, 96}, + byte_total_case{&kahip::serial_kernel::profile_byte_accounting::edge_wire_bytes, + &profile_limits::edge_wire_byte_limit, + byte_accounting_stage::edge_wire, 64}, + byte_total_case{&kahip::serial_kernel::profile_byte_accounting::wire_record_bytes, + &profile_limits::wire_record_byte_limit, + byte_accounting_stage::wire_record, 160}, + byte_total_case{&kahip::serial_kernel::profile_byte_accounting::xadj_bytes, + &profile_limits::xadj_byte_limit, + byte_accounting_stage::xadj, 16}, + byte_total_case{&kahip::serial_kernel::profile_byte_accounting::adjacency_bytes, + &profile_limits::adjacency_byte_limit, + byte_accounting_stage::adjacency, 16}, + byte_total_case{&kahip::serial_kernel::profile_byte_accounting::node_weight_bytes, + &profile_limits::node_weight_byte_limit, + byte_accounting_stage::node_weight, 12}, + byte_total_case{&kahip::serial_kernel::profile_byte_accounting::edge_weight_bytes, + &profile_limits::edge_weight_byte_limit, + byte_accounting_stage::edge_weight, 16}, + byte_total_case{&kahip::serial_kernel::profile_byte_accounting::csr_bytes, + &profile_limits::csr_byte_limit, + byte_accounting_stage::csr, 60}, + byte_total_case{&kahip::serial_kernel::profile_byte_accounting::partition_bytes, + &profile_limits::partition_byte_limit, + byte_accounting_stage::partition, 12}, + byte_total_case{&kahip::serial_kernel::profile_byte_accounting::serial_input_bytes, + &profile_limits::serial_input_byte_limit, + byte_accounting_stage::serial_input, 72}, + byte_total_case{&kahip::serial_kernel::profile_byte_accounting::complete_node_bytes, + &profile_limits::complete_node_byte_limit, + byte_accounting_stage::complete_node, 32}, + byte_total_case{&kahip::serial_kernel::profile_byte_accounting::complete_node_data_bytes, + &profile_limits::complete_node_data_byte_limit, + byte_accounting_stage::complete_node_data, 96}, + byte_total_case{&kahip::serial_kernel::profile_byte_accounting::complete_edge_bytes, + &profile_limits::complete_edge_byte_limit, + byte_accounting_stage::complete_edge, 64}, + byte_total_case{&kahip::serial_kernel::profile_byte_accounting::complete_graph_bytes, + &profile_limits::complete_graph_byte_limit, + byte_accounting_stage::complete_graph, 192}, + byte_total_case{&kahip::serial_kernel::profile_byte_accounting::structural_validation_bytes, + &profile_limits::structural_validation_byte_limit, + byte_accounting_stage::structural_validation, 96}, + }; + for (auto const& test : totals) { + CHECK(accounting.*(test.member) == test.exact); + auto at_limit = profile_limits::native(); + at_limit.*(test.limit) = test.exact; + CHECK(kahip::serial_kernel::account_profile_bytes(input, at_limit).safe()); + at_limit.*(test.limit) = test.exact - 1; + CHECK(kahip::serial_kernel::account_profile_bytes(input, at_limit).stage == + test.rejection_stage); + } + auto base_limit = profile_limits::native(); + base_limit.base_memory_byte_limit = 448; + CHECK(kahip::serial_kernel::account_profile_bytes(input, base_limit).safe()); + base_limit.base_memory_byte_limit = 447; + CHECK(kahip::serial_kernel::account_profile_bytes(input, base_limit).stage == + byte_accounting_stage::base_memory); +} + +TEST_CASE("serial-kernel profile has independent exact vector boundaries", + "[serial-kernel][profile]") { + auto const input = profile_input{ + .global_nodes = 2, + .global_directed_edges = 2, + .total_node_weight = 2, + .maximum_node_weight = 1, + .total_directed_edge_weight = 2, + .maximum_directed_edge_weight = 1, + .block_count = 1, + .absolute_bound = 2, + }; + auto check_boundary = [&](auto member, std::uint64_t required) { + auto limits = profile_limits::native(); + limits.*member = required; + CHECK(kahip::serial_kernel::make_profile(input, limits).safe()); + limits.*member = required - 1; + CHECK(kahip::serial_kernel::make_profile(input, limits).reason == + profile_reason::vector_capacity_exceeded); + }; + check_boundary(&profile_limits::xadj_elements, 3); + check_boundary(&profile_limits::adjncy_elements, 2); + check_boundary(&profile_limits::node_weight_elements, 2); + check_boundary(&profile_limits::edge_weight_elements, 2); + check_boundary(&profile_limits::partition_elements, 2); + check_boundary(&profile_limits::wire_node_elements, 2); + check_boundary(&profile_limits::wire_edge_elements, 2); + check_boundary(&profile_limits::complete_node_elements, 3); + check_boundary(&profile_limits::complete_node_data_elements, 3); + check_boundary(&profile_limits::complete_edge_elements, 2); + check_boundary(&profile_limits::structural_validation_elements, 2); + check_boundary(&profile_limits::flat_payload_elements, 9); +} + +TEST_CASE("serial-kernel profile exposes flag and mode-product boundaries", + "[serial-kernel][profile]") { + auto input = profile_input{ + .global_nodes = 2, + .global_directed_edges = 0, + .total_node_weight = 2, + .maximum_node_weight = 1, + .block_count = 2, + .absolute_bound = 1, + }; + input.csr_offsets_are_valid = false; + CHECK(kahip::serial_kernel::make_profile(input).reason == + profile_reason::csr_offset_out_of_range); + input.csr_offsets_are_valid = true; + input.targets_are_valid = false; + CHECK(kahip::serial_kernel::make_profile(input).reason == + profile_reason::target_out_of_range); + + input.targets_are_valid = true; + input.social_mode = true; + auto limits = profile_limits::native(); + limits.modified_node_weight_max = 10'000; + CHECK(kahip::serial_kernel::make_profile(input, limits).safe()); + input.global_nodes = 3; + input.total_node_weight = 3; + input.block_count = 3; + CHECK(kahip::serial_kernel::make_profile(input, limits).reason == + profile_reason::stop_rule_domain_out_of_range); + + input.block_count = 2; + input.social_mode = false; + limits = profile_limits::native(); + limits.int_max = 8; + CHECK(kahip::serial_kernel::make_profile(input, limits).safe()); + input.block_count = 3; + CHECK(kahip::serial_kernel::make_profile(input, limits).reason == + profile_reason::stop_rule_domain_out_of_range); + + input.global_nodes = 2; + input.total_node_weight = 2; + input.block_count = 2; + limits = profile_limits::native(); + limits.modified_node_weight_max = 120; + CHECK(kahip::serial_kernel::make_profile(input, limits).safe()); + limits.modified_node_weight_max = 119; + CHECK(kahip::serial_kernel::make_profile(input, limits).reason == + profile_reason::stop_rule_domain_out_of_range); +} + +TEST_CASE("range owner lookup matches the legacy oracle at degenerate boundaries", + "[serial-kernel][owner]") { + auto const legacy_owner = [](auto const& ranges, std::uint64_t node) { + for (int pe = 1; pe < static_cast(ranges.size()); ++pe) { + if (node < ranges[static_cast(pe)]) { + return pe - 1; + } + } + return -1; + }; + auto check = [&](auto const& ranges, auto const& nodes) { + for (auto node : nodes) { + CHECK(kahip::range_owner::from_boundaries(ranges, node) == + legacy_owner(ranges, node)); + } + }; + check(std::array{0}, + std::array{0, 1, 99}); + check(std::array{7, 8}, + std::array{6, 7, 8, 99}); + check(std::array{0, 0, 0, 0}, + std::array{0, 1, 99}); + check(std::array{0, 2, 2, 2, 2}, + std::array{0, 1, 2, 3, 99}); +} + +TEST_CASE("range owner lookup preserves the legacy scan across empty ranks", + "[serial-kernel][owner]") { + auto const legacy_owner = [](std::array const& ranges, + std::uint64_t node) { + for (int pe = 1; pe < static_cast(ranges.size()); ++pe) { + if (node < ranges[static_cast(pe)]) { + return pe - 1; + } + } + return -1; + }; + auto const ranges = std::array{0, 0, 2, 2, 5}; + + for (auto const node : std::array{0, 1, 2, 3, 4, 5, 6, + 99}) { + CHECK(kahip::range_owner::from_boundaries(ranges, node) == + legacy_owner(ranges, node)); + } +} + +TEST_CASE("range owner lookup retains first and last ordinary owners", + "[serial-kernel][owner]") { + auto const ranges = std::array{10, 13, 16, 19}; + + CHECK(kahip::range_owner::from_boundaries(ranges, 9) == 0); + CHECK(kahip::range_owner::from_boundaries(ranges, 10) == 0); + CHECK(kahip::range_owner::from_boundaries(ranges, 12) == 0); + CHECK(kahip::range_owner::from_boundaries(ranges, 13) == 1); + CHECK(kahip::range_owner::from_boundaries(ranges, 18) == 2); + CHECK(kahip::range_owner::from_boundaries(ranges, 19) == -1); +} + +TEST_CASE("serial kernel accepts only reciprocal loop-free weighted arcs", + "[serial-kernel][structure]") { + using kahip::serial_kernel::directed_arc; + auto const valid_unsorted = std::vector{ + {1, 0, 7}, {0, 2, 3}, {2, 0, 3}, {0, 1, 7}, + {1, 0, 7}, {0, 1, 7}}; + CHECK(kahip::serial_kernel::is_loop_free_reciprocal_undirected( + valid_unsorted)); + CHECK_FALSE(kahip::serial_kernel::is_loop_free_reciprocal_undirected( + std::vector{{0, 0, 1}})); + CHECK_FALSE(kahip::serial_kernel::is_loop_free_reciprocal_undirected( + std::vector{{0, 1, 1}})); + CHECK_FALSE(kahip::serial_kernel::is_loop_free_reciprocal_undirected( + std::vector{{0, 1, 1}, {1, 0, 2}})); + CHECK_FALSE(kahip::serial_kernel::is_loop_free_reciprocal_undirected( + std::vector{{0, 1, 1}, {1, 0, 1}, {0, 1, 1}})); +} + +TEST_CASE("single-block bridge avoids the undefined generic kernel domain", + "[serial-kernel][bridge]") { + auto partition = std::array{9, 8, 7}; + auto edgecut = -1; + auto balance = -1.0; + CHECK(kahip::serial_kernel::solve_trivial_single_block( + 1, partition, edgecut, balance)); + CHECK(partition == std::array{0, 0, 0}); + CHECK(edgecut == 0); + CHECK(balance == 1.0); + CHECK_FALSE(kahip::serial_kernel::solve_trivial_single_block( + 2, partition, edgecut, balance)); +} +} // namespace diff --git a/parallel/parallel_src/tests/distributed_partitioning/verify_distributed_partitioner_failure.cmake b/parallel/parallel_src/tests/distributed_partitioning/verify_distributed_partitioner_failure.cmake new file mode 100644 index 00000000..c62bc476 --- /dev/null +++ b/parallel/parallel_src/tests/distributed_partitioning/verify_distributed_partitioner_failure.cmake @@ -0,0 +1,69 @@ +cmake_minimum_required(VERSION 4.0) + +if( + NOT DEFINED MPIEXEC_EXECUTABLE + OR NOT DEFINED MPIEXEC_NUMPROC_FLAG + OR NOT DEFINED PROBE + OR NOT DEFINED MODE + OR NOT DEFINED EXPECTED_DIAGNOSTIC +) + message( + FATAL_ERROR + "MPI launcher, PROBE, MODE, and EXPECTED_DIAGNOSTIC are required" + ) +endif() + +execute_process( + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 2 + ${MPIEXEC_PREFLAGS} + "${PROBE}" "${MODE}" + ${MPIEXEC_POSTFLAGS} + RESULT_VARIABLE probe_result + OUTPUT_VARIABLE probe_stdout + ERROR_VARIABLE probe_stderr + TIMEOUT 15 +) + +set(probe_output "${probe_stdout}\n${probe_stderr}") +if("${probe_result}" STREQUAL "0") + message(FATAL_ERROR "failure probe returned success\n${probe_output}") +endif() +if("${probe_result}" MATCHES "[Tt]imeout") + message(FATAL_ERROR "failure probe timed out\n${probe_output}") +endif() + +string( + REGEX MATCHALL + "observed distributed-partitioner MPI_Abort on affected communicator before graph mutation" + abort_markers + "${probe_output}" +) +list(LENGTH abort_markers abort_count) +if(NOT abort_count EQUAL 2) + message( + FATAL_ERROR + "expected exactly two affected-communicator abort markers; found ${abort_count}\n${probe_output}" + ) +endif() + +string(FIND "${probe_output}" "${EXPECTED_DIAGNOSTIC}" diagnostic_offset) +if(diagnostic_offset EQUAL -1) + message( + FATAL_ERROR + "missing distributed-partitioner failure diagnostic '${EXPECTED_DIAGNOSTIC}'\n${probe_output}" + ) +endif() + +foreach( + forbidden + IN ITEMS + "unexpected state" + "returned without fail-fast termination" + "MPI_Finalize" +) + string(FIND "${probe_output}" "${forbidden}" forbidden_offset) + if(NOT forbidden_offset EQUAL -1) + message(FATAL_ERROR "failure probe used ${forbidden}\n${probe_output}") + endif() +endforeach() diff --git a/parallel/parallel_src/tests/dspac/first_split_failure_probe.cpp b/parallel/parallel_src/tests/dspac/first_split_failure_probe.cpp new file mode 100644 index 00000000..d453fb2d --- /dev/null +++ b/parallel/parallel_src/tests/dspac/first_split_failure_probe.cpp @@ -0,0 +1,320 @@ +#include +#include + +#include +#include +#include +#include +#include + +#include "data_structure/parallel_graph_access.h" +#include "definitions.h" +#include "dspac/dspac.h" +#include "kahip_mpi_capabilities.h" + +// KAHIP_PMPI_CALLBACK_REGION_BEGIN +namespace dspac_first_split_failure_probe { +enum class failure_mode { neighbor_payload, projection_permutation, projection_barrier }; + +inline bool active = false; +inline failure_mode selected = failure_mode::neighbor_payload; +inline MPI_Comm expected_communicator = MPI_COMM_NULL; +inline int topology_creations = 0; +inline int count_exchanges = 0; +inline int payload_calls = 0; +inline int barrier_calls = 0; +inline int point_to_point_calls = 0; +inline int finalizations = 0; +inline bool callback_error = false; + +void write_text(std::string_view text) noexcept { + static_cast(::write(STDERR_FILENO, text.data(), text.size())); +} + +[[nodiscard]] auto expected_abort_state() noexcept -> bool { + if (callback_error || point_to_point_calls != 0 || finalizations != 0) { + return false; + } + switch (selected) { + case failure_mode::neighbor_payload: + return topology_creations == 1 && count_exchanges == 1 && + payload_calls == 1 && barrier_calls == 0; + case failure_mode::projection_permutation: + return topology_creations == 0 && count_exchanges == 0 && + payload_calls == 0 && barrier_calls == 0; + case failure_mode::projection_barrier: + return topology_creations == 0 && count_exchanges == 0 && + payload_calls == 0 && barrier_calls == 1; + } + return false; +} + +[[noreturn]] void observed_abort(MPI_Comm communicator, + int error_code) noexcept { + auto relation = int{MPI_UNEQUAL}; + if (error_code != EXIT_FAILURE || + PMPI_Comm_compare(communicator, expected_communicator, &relation) != + MPI_SUCCESS || + (relation != MPI_CONGRUENT && relation != MPI_IDENT) || + !expected_abort_state()) { + write_text("observed DSPAC first-split MPI_Abort with unexpected state\n"); + std::_Exit(91); + } + write_text("observed DSPAC MPI_Abort on affected communicator\n"); + if (PMPI_Barrier(MPI_COMM_WORLD) != MPI_SUCCESS) { + std::_Exit(90); + } + std::_Exit(86); +} +} // namespace dspac_first_split_failure_probe + +static_assert( + noexcept(dspac_first_split_failure_probe::write_text(std::string_view{}))); +static_assert( + noexcept(dspac_first_split_failure_probe::expected_abort_state())); +static_assert(noexcept(dspac_first_split_failure_probe::observed_abort( + MPI_COMM_NULL, 0))); + +extern "C" int MPI_Dist_graph_create(MPI_Comm communicator, + int source_count, + int const sources[], + int const degrees[], + int const destinations[], + int const weights[], + MPI_Info info, + int reorder, + MPI_Comm* graph_communicator) { + if (dspac_first_split_failure_probe::active) { + ++dspac_first_split_failure_probe::topology_creations; + } + return PMPI_Dist_graph_create(communicator, source_count, sources, degrees, + destinations, weights, info, reorder, + graph_communicator); +} + +extern "C" int MPI_Neighbor_alltoall(void const* send_buffer, + int send_count, + MPI_Datatype send_datatype, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (dspac_first_split_failure_probe::active) { + ++dspac_first_split_failure_probe::count_exchanges; + } + return PMPI_Neighbor_alltoall(send_buffer, send_count, send_datatype, + receive_buffer, receive_count, + receive_datatype, communicator); +} + +extern "C" int MPI_Neighbor_alltoallv(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (!dspac_first_split_failure_probe::active || + dspac_first_split_failure_probe::selected != + dspac_first_split_failure_probe::failure_mode::neighbor_payload) { + return PMPI_Neighbor_alltoallv( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, + receive_datatype, communicator); + } + ++dspac_first_split_failure_probe::payload_calls; + if (send_counts == nullptr || receive_counts == nullptr || + send_datatype != MPI_UNSIGNED_LONG_LONG || + receive_datatype != MPI_UNSIGNED_LONG_LONG) { + dspac_first_split_failure_probe::callback_error = true; + } + return MPI_ERR_OTHER; +} + +#if defined(KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C) && \ + KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C +extern "C" int MPI_Neighbor_alltoallv_c( + void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (!dspac_first_split_failure_probe::active || + dspac_first_split_failure_probe::selected != + dspac_first_split_failure_probe::failure_mode::neighbor_payload) { + return PMPI_Neighbor_alltoallv_c( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, + receive_datatype, communicator); + } + ++dspac_first_split_failure_probe::payload_calls; + if (send_counts == nullptr || receive_counts == nullptr || + send_datatype != MPI_UNSIGNED_LONG_LONG || + receive_datatype != MPI_UNSIGNED_LONG_LONG) { + dspac_first_split_failure_probe::callback_error = true; + } + return MPI_ERR_OTHER; +} +#endif + +extern "C" int MPI_Isend(void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator, + MPI_Request* request) { + if (dspac_first_split_failure_probe::active) { + ++dspac_first_split_failure_probe::point_to_point_calls; + } + return PMPI_Isend(buffer, count, datatype, destination, tag, communicator, + request); +} + +extern "C" int MPI_Irecv(void* buffer, + int count, + MPI_Datatype datatype, + int source, + int tag, + MPI_Comm communicator, + MPI_Request* request) { + if (dspac_first_split_failure_probe::active) { + ++dspac_first_split_failure_probe::point_to_point_calls; + } + return PMPI_Irecv(buffer, count, datatype, source, tag, communicator, + request); +} + +extern "C" int MPI_Recv(void* buffer, + int count, + MPI_Datatype datatype, + int source, + int tag, + MPI_Comm communicator, + MPI_Status* status) { + if (dspac_first_split_failure_probe::active) { + ++dspac_first_split_failure_probe::point_to_point_calls; + } + return PMPI_Recv(buffer, count, datatype, source, tag, communicator, status); +} + +extern "C" int MPI_Wait(MPI_Request* request, MPI_Status* status) { + if (dspac_first_split_failure_probe::active) { + ++dspac_first_split_failure_probe::point_to_point_calls; + } + return PMPI_Wait(request, status); +} + +extern "C" int MPI_Barrier(MPI_Comm communicator) { + if (dspac_first_split_failure_probe::active && + dspac_first_split_failure_probe::selected == + dspac_first_split_failure_probe::failure_mode::projection_barrier) { + ++dspac_first_split_failure_probe::barrier_calls; + return MPI_ERR_OTHER; + } + return PMPI_Barrier(communicator); +} + +extern "C" int MPI_Finalize() { + if (dspac_first_split_failure_probe::active) { + ++dspac_first_split_failure_probe::finalizations; + return MPI_SUCCESS; + } + return PMPI_Finalize(); +} + +extern "C" int MPI_Abort(MPI_Comm communicator, int error_code) { + dspac_first_split_failure_probe::observed_abort(communicator, error_code); +} +// KAHIP_PMPI_CALLBACK_REGION_END + +namespace { +[[nodiscard]] auto parse_mode(char const* value) + -> dspac_first_split_failure_probe::failure_mode { + auto const name = std::string_view{value}; + if (name == "neighbor-payload") { + return dspac_first_split_failure_probe::failure_mode::neighbor_payload; + } + if (name == "projection-permutation") { + return dspac_first_split_failure_probe::failure_mode::projection_permutation; + } + if (name == "projection-barrier") { + return dspac_first_split_failure_probe::failure_mode::projection_barrier; + } + std::exit(6); +} + +void build_fixture(parhip::parallel_graph_access& graph, int rank) { + constexpr auto node_ranges = std::array{0, 1, 2}; + constexpr auto edge_ranges = std::array{0, 1, 2}; + graph.start_construction(1, 1, 2, 2, false); + graph.set_range(node_ranges[static_cast(rank)], + node_ranges[static_cast(rank)]); + auto mutable_node_ranges = + std::vector(node_ranges.begin(), node_ranges.end()); + graph.set_range_array(mutable_node_ranges); + graph.set_edge_range_array( + std::vector(edge_ranges.begin(), edge_ranges.end())); + + auto const local = graph.new_node(); + graph.setNodeWeight(local, 1); + graph.setNodeLabel(local, static_cast(rank)); + auto const edge = graph.new_edge(local, static_cast(1 - rank)); + graph.setEdgeWeight(edge, 1); + graph.finish_construction(); +} +} // namespace + +int main(int argc, char** argv) { + if (argc != 2) { + return 1; + } + dspac_first_split_failure_probe::selected = parse_mode(argv[1]); + if (MPI_Init(&argc, &argv) != MPI_SUCCESS) { + return 2; + } + auto rank = -1; + auto size = 0; + if (MPI_Comm_rank(MPI_COMM_WORLD, &rank) != MPI_SUCCESS || + MPI_Comm_size(MPI_COMM_WORLD, &size) != MPI_SUCCESS || size != 2) { + return 3; + } + if (MPI_Comm_set_errhandler(MPI_COMM_WORLD, MPI_ERRORS_RETURN) != + MPI_SUCCESS) { + return 4; + } + + auto input = parhip::parallel_graph_access{MPI_COMM_WORLD}; + auto split = parhip::parallel_graph_access{MPI_COMM_WORLD}; + build_fixture(input, rank); + auto splitter = parhip::dspac{ + input, MPI_COMM_WORLD, + std::numeric_limits::max()}; + + auto const neighbor_payload_failure = + dspac_first_split_failure_probe::selected == + dspac_first_split_failure_probe::failure_mode::neighbor_payload; + dspac_first_split_failure_probe::expected_communicator = MPI_COMM_WORLD; + dspac_first_split_failure_probe::active = neighbor_payload_failure; + splitter.construct(split); + + dspac_first_split_failure_probe::active = true; + if (dspac_first_split_failure_probe::selected == + dspac_first_split_failure_probe::failure_mode::projection_permutation) { + static_cast(splitter.project_partition(split, {1})); + } else if (dspac_first_split_failure_probe::selected == + dspac_first_split_failure_probe::failure_mode::projection_barrier) { + static_cast(splitter.project_partition(split, {0})); + } + + dspac_first_split_failure_probe::write_text( + "DSPAC failure returned without fail-fast\n"); + static_cast(MPI_Finalize()); + return 5; +} diff --git a/parallel/parallel_src/tests/dspac/first_split_mpi_test.cpp b/parallel/parallel_src/tests/dspac/first_split_mpi_test.cpp new file mode 100644 index 00000000..355c1492 --- /dev/null +++ b/parallel/parallel_src/tests/dspac/first_split_mpi_test.cpp @@ -0,0 +1,544 @@ +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_collectives.h" +#include "data_structure/parallel_graph_access.h" +#include "definitions.h" +#include "dspac/dspac.h" +#include "kahip_mpi_capabilities.h" + +// KAHIP_PMPI_CALLBACK_REGION_BEGIN +namespace dspac_first_split_probe { +inline bool active = false; +inline int topology_creations = 0; +inline int count_exchanges = 0; +inline int legacy_payloads = 0; +inline int large_count_payloads = 0; +inline int point_to_point_calls = 0; +inline int maximum_payload_count = 0; +inline bool payload_signature_is_valid = true; + +void reset() noexcept { + topology_creations = 0; + count_exchanges = 0; + legacy_payloads = 0; + large_count_payloads = 0; + point_to_point_calls = 0; + maximum_payload_count = 0; + payload_signature_is_valid = true; +} + +void record_legacy_payload(int const send_counts[], + int const receive_counts[], + MPI_Datatype send_datatype, + MPI_Datatype receive_datatype, + MPI_Comm communicator) noexcept { + ++legacy_payloads; + auto indegree = 0; + auto outdegree = 0; + auto weighted = 0; + if (PMPI_Dist_graph_neighbors_count(communicator, &indegree, &outdegree, + &weighted) != MPI_SUCCESS) { + payload_signature_is_valid = false; + return; + } + payload_signature_is_valid = + payload_signature_is_valid && + (outdegree == 0 || send_counts != nullptr) && + (indegree == 0 || receive_counts != nullptr) && + send_datatype == MPI_UNSIGNED_LONG_LONG && + receive_datatype == MPI_UNSIGNED_LONG_LONG; + for (int index = 0; index < outdegree; ++index) { + maximum_payload_count = + std::max(maximum_payload_count, send_counts[index]); + } + for (int index = 0; index < indegree; ++index) { + maximum_payload_count = + std::max(maximum_payload_count, receive_counts[index]); + } +} + +#if defined(KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C) && \ + KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C +void record_large_count_payload(MPI_Count const send_counts[], + MPI_Count const receive_counts[], + MPI_Datatype send_datatype, + MPI_Datatype receive_datatype, + MPI_Comm communicator) noexcept { + ++large_count_payloads; + auto indegree = 0; + auto outdegree = 0; + auto weighted = 0; + if (PMPI_Dist_graph_neighbors_count(communicator, &indegree, &outdegree, + &weighted) != MPI_SUCCESS) { + payload_signature_is_valid = false; + return; + } + payload_signature_is_valid = + payload_signature_is_valid && + (outdegree == 0 || send_counts != nullptr) && + (indegree == 0 || receive_counts != nullptr) && + send_datatype == MPI_UNSIGNED_LONG_LONG && + receive_datatype == MPI_UNSIGNED_LONG_LONG; + for (int index = 0; index < outdegree; ++index) { + if (send_counts[index] > std::numeric_limits::max()) { + maximum_payload_count = std::numeric_limits::max(); + } else if (send_counts[index] > maximum_payload_count) { + maximum_payload_count = static_cast(send_counts[index]); + } + } + for (int index = 0; index < indegree; ++index) { + if (receive_counts[index] > std::numeric_limits::max()) { + maximum_payload_count = std::numeric_limits::max(); + } else if (receive_counts[index] > maximum_payload_count) { + maximum_payload_count = static_cast(receive_counts[index]); + } + } +} +#endif + +class activation final { + public: + activation() noexcept { + reset(); + active = true; + } + ~activation() noexcept { active = false; } + + activation(activation const&) = delete; + auto operator=(activation const&) -> activation& = delete; +}; +} // namespace dspac_first_split_probe + +static_assert(noexcept(dspac_first_split_probe::reset())); +static_assert(noexcept(dspac_first_split_probe::record_legacy_payload( + nullptr, nullptr, MPI_DATATYPE_NULL, MPI_DATATYPE_NULL, MPI_COMM_NULL))); +#if defined(KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C) && \ + KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C +static_assert(noexcept(dspac_first_split_probe::record_large_count_payload( + nullptr, nullptr, MPI_DATATYPE_NULL, MPI_DATATYPE_NULL, MPI_COMM_NULL))); +#endif + +extern "C" int MPI_Dist_graph_create(MPI_Comm communicator, + int source_count, + int const sources[], + int const degrees[], + int const destinations[], + int const weights[], + MPI_Info info, + int reorder, + MPI_Comm* graph_communicator) { + if (dspac_first_split_probe::active) { + ++dspac_first_split_probe::topology_creations; + } + return PMPI_Dist_graph_create(communicator, source_count, sources, degrees, + destinations, weights, info, reorder, + graph_communicator); +} + +extern "C" int MPI_Neighbor_alltoall(void const* send_buffer, + int send_count, + MPI_Datatype send_datatype, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (dspac_first_split_probe::active) { + ++dspac_first_split_probe::count_exchanges; + } + return PMPI_Neighbor_alltoall(send_buffer, send_count, send_datatype, + receive_buffer, receive_count, + receive_datatype, communicator); +} + +extern "C" int MPI_Neighbor_alltoallv(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (dspac_first_split_probe::active) { + dspac_first_split_probe::record_legacy_payload( + send_counts, receive_counts, send_datatype, receive_datatype, + communicator); + } + return PMPI_Neighbor_alltoallv(send_buffer, send_counts, send_displacements, + send_datatype, receive_buffer, receive_counts, + receive_displacements, receive_datatype, + communicator); +} + +#if defined(KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C) && \ + KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C +extern "C" int MPI_Neighbor_alltoallv_c( + void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (dspac_first_split_probe::active) { + dspac_first_split_probe::record_large_count_payload( + send_counts, receive_counts, send_datatype, receive_datatype, + communicator); + } + return PMPI_Neighbor_alltoallv_c( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator); +} +#endif + +extern "C" int MPI_Isend(void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator, + MPI_Request* request) { + if (dspac_first_split_probe::active) { + ++dspac_first_split_probe::point_to_point_calls; + } + return PMPI_Isend(buffer, count, datatype, destination, tag, communicator, + request); +} + +extern "C" int MPI_Irecv(void* buffer, + int count, + MPI_Datatype datatype, + int source, + int tag, + MPI_Comm communicator, + MPI_Request* request) { + if (dspac_first_split_probe::active) { + ++dspac_first_split_probe::point_to_point_calls; + } + return PMPI_Irecv(buffer, count, datatype, source, tag, communicator, + request); +} + +extern "C" int MPI_Recv(void* buffer, + int count, + MPI_Datatype datatype, + int source, + int tag, + MPI_Comm communicator, + MPI_Status* status) { + if (dspac_first_split_probe::active) { + ++dspac_first_split_probe::point_to_point_calls; + } + return PMPI_Recv(buffer, count, datatype, source, tag, communicator, status); +} + +extern "C" int MPI_Wait(MPI_Request* request, MPI_Status* status) { + if (dspac_first_split_probe::active) { + ++dspac_first_split_probe::point_to_point_calls; + } + return PMPI_Wait(request, status); +} +// KAHIP_PMPI_CALLBACK_REGION_END + +namespace { +using adjacency_list = std::vector>; + +struct graph_fixture final { + std::vector node_ranges; + std::vector edge_ranges; + adjacency_list adjacency; +}; + +struct expected_edge final { + parhip::NodeID target; + parhip::EdgeWeight weight; + + auto operator==(expected_edge const&) const -> bool = default; +}; + +[[nodiscard]] auto make_fixture(adjacency_list adjacency, + int size) -> graph_fixture { + auto const global_nodes = static_cast(adjacency.size()); + auto node_ranges = + std::vector(static_cast(size) + 1); + for (int pe = 0; pe <= size; ++pe) { + node_ranges[static_cast(pe)] = + global_nodes * static_cast(pe) / + static_cast(size); + } + + auto edge_ranges = + std::vector(static_cast(size) + 1); + for (int pe = 0; pe < size; ++pe) { + auto extent = parhip::EdgeID{}; + for (auto node = node_ranges[static_cast(pe)]; + node < node_ranges[static_cast(pe) + 1]; ++node) { + extent += static_cast( + adjacency[static_cast(node)].size()); + } + edge_ranges[static_cast(pe) + 1] = + edge_ranges[static_cast(pe)] + extent; + } + return {std::move(node_ranges), std::move(edge_ranges), + std::move(adjacency)}; +} + +void build_graph(parhip::parallel_graph_access& graph, + graph_fixture const& fixture, + int rank) { + auto const first = fixture.node_ranges[static_cast(rank)]; + auto const end = fixture.node_ranges[static_cast(rank) + 1]; + auto const local_nodes = end - first; + auto const local_edges = + fixture.edge_ranges[static_cast(rank) + 1] - + fixture.edge_ranges[static_cast(rank)]; + auto const global_nodes = + static_cast(fixture.adjacency.size()); + auto const global_edges = fixture.edge_ranges.back(); + + graph.start_construction(local_nodes, local_edges, global_nodes, + global_edges, false); + graph.set_range(first, first == end ? first : end - parhip::NodeID{1}); + auto node_ranges = fixture.node_ranges; + graph.set_range_array(node_ranges); + graph.set_edge_range_array(fixture.edge_ranges); + + for (auto global = first; global < end; ++global) { + auto const local = graph.new_node(); + graph.setNodeWeight(local, 1); + graph.setNodeLabel(local, global); + graph.setSecondPartitionIndex(local, 0); + for (auto const target : + fixture.adjacency[static_cast(global)]) { + auto const edge = graph.new_edge(local, target); + graph.setEdgeWeight(edge, 1); + } + } + graph.finish_construction(); +} + +void require_exact_split( + parhip::parallel_graph_access& split, + graph_fixture const& fixture, + int rank, + std::span const> expected) { + auto const first = fixture.edge_ranges[static_cast(rank)]; + auto const end = fixture.edge_ranges[static_cast(rank) + 1]; + auto const local_nodes = end - first; + auto expected_local_edges = parhip::EdgeID{}; + for (auto global = first; global < end; ++global) { + expected_local_edges += static_cast( + expected[static_cast(global)].size()); + } + auto const expected_global_edges = std::ranges::fold_left( + expected | std::views::transform(&std::vector::size), + parhip::EdgeID{}, std::plus<>{}); + + REQUIRE(split.number_of_local_nodes() == local_nodes); + REQUIRE(split.number_of_local_edges() == expected_local_edges); + REQUIRE(split.number_of_global_nodes() == expected.size()); + REQUIRE(split.number_of_global_edges() == expected_global_edges); + REQUIRE(split.get_from_range() == first); + REQUIRE(split.get_to_range() == (first == end ? first : end - 1)); + REQUIRE(split.get_range_array() == fixture.edge_ranges); + + for (auto local = parhip::NodeID{}; local < local_nodes; ++local) { + auto const global = first + local; + REQUIRE(split.getNodeWeight(local) == 1); + REQUIRE(split.getNodeLabel(local) == global); + REQUIRE(split.getSecondPartitionIndex(local) == 0); + + auto actual = std::vector{}; + for (auto edge = split.get_first_edge(local); + edge < split.get_first_invalid_edge(local); ++edge) { + auto const target = split.getEdgeTarget(edge); + actual.push_back( + {split.getGlobalID(target), split.getEdgeWeight(edge)}); + } + REQUIRE(actual == expected[static_cast(global)]); + } +} + +void require_exact_reverse_projection(parhip::dspac& splitter, + parhip::parallel_graph_access& split, + graph_fixture const& fixture, + int rank) { + auto const first = fixture.edge_ranges[static_cast(rank)]; + auto const end = fixture.edge_ranges[static_cast(rank) + 1]; + auto const local_edge_count = static_cast(end - first); + auto permutation = std::vector(local_edge_count); + std::ranges::iota(permutation, parhip::EdgeID{}); + std::ranges::reverse(permutation); + + auto expected = std::vector(local_edge_count); + for (std::size_t edge = 0; edge < local_edge_count; ++edge) { + expected[static_cast(permutation[edge])] = + static_cast(first + edge); + } + + REQUIRE(splitter.project_partition(split, permutation) == expected); +} + +[[nodiscard]] auto path_with_isolate_fixture(int size) -> graph_fixture { + return make_fixture({{1, 2}, {0}, {0, 3}, {2}, {}}, size); +} + +[[nodiscard]] auto path_with_isolate_expected(parhip::EdgeWeight infinity) + -> std::array, 6> { + return {{ + {{2, infinity}, {1, 1}}, + {{3, infinity}, {0, 1}}, + {{0, infinity}}, + {{1, infinity}, {4, 1}}, + {{5, infinity}, {3, 1}}, + {{4, infinity}}, + }}; +} + +[[nodiscard]] auto two_node_fixture(int size) -> graph_fixture { + return make_fixture({{1}, {0}}, size); +} + +[[nodiscard]] auto two_node_expected(parhip::EdgeWeight infinity) + -> std::array, 2> { + return {{{{1, infinity}}, {{0, infinity}}}}; +} + +void require_collective_protocol(bool single_payload = true) { + REQUIRE(dspac_first_split_probe::topology_creations == 1); + REQUIRE(dspac_first_split_probe::count_exchanges == 1); + auto const payload_calls = dspac_first_split_probe::legacy_payloads + + dspac_first_split_probe::large_count_payloads; + REQUIRE(payload_calls >= 1); + if (single_payload) { + REQUIRE(payload_calls == 1); + } + REQUIRE(dspac_first_split_probe::point_to_point_calls == 0); + REQUIRE(dspac_first_split_probe::payload_signature_is_valid); +} +} // namespace + +TEST_CASE("DSPAC first split preserves the exact split graph with neighborhood collectives") { + auto rank = -1; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE(size >= 1); + REQUIRE(size <= 5); + + constexpr auto infinity = std::numeric_limits::max(); + auto const fixture = path_with_isolate_fixture(size); + auto const expected = path_with_isolate_expected(infinity); + auto input = parhip::parallel_graph_access{MPI_COMM_WORLD}; + auto split = parhip::parallel_graph_access{MPI_COMM_WORLD}; + build_graph(input, fixture, rank); + auto splitter = parhip::dspac{input, MPI_COMM_WORLD, infinity}; + + { + dspac_first_split_probe::activation const probe; + splitter.construct(split); + } + + require_exact_split(split, fixture, rank, expected); + require_exact_reverse_projection(splitter, split, fixture, rank); + require_collective_protocol(); +} + +TEST_CASE("DSPAC first split supports zero-local-work ranks") { + auto rank = -1; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + + constexpr auto infinity = parhip::EdgeWeight{37}; + auto const fixture = two_node_fixture(size); + auto const expected = two_node_expected(infinity); + auto input = parhip::parallel_graph_access{MPI_COMM_WORLD}; + auto split = parhip::parallel_graph_access{MPI_COMM_WORLD}; + build_graph(input, fixture, rank); + auto splitter = parhip::dspac{input, MPI_COMM_WORLD, infinity}; + + { + dspac_first_split_probe::activation const probe; + splitter.construct(split); + } + + require_exact_split(split, fixture, rank, expected); + require_exact_reverse_projection(splitter, split, fixture, rank); + require_collective_protocol(); + if (size > 2 && fixture.node_ranges[static_cast(rank)] == + fixture.node_ranges[static_cast(rank) + 1]) { + REQUIRE(input.number_of_local_nodes() == 0); + } +} + +TEST_CASE("DSPAC first split preserves a globally empty graph") { + auto rank = -1; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + + constexpr auto infinity = parhip::EdgeWeight{91}; + auto const fixture = make_fixture({}, size); + auto const expected = std::array, 0>{}; + auto input = parhip::parallel_graph_access{MPI_COMM_WORLD}; + auto split = parhip::parallel_graph_access{MPI_COMM_WORLD}; + build_graph(input, fixture, rank); + auto splitter = parhip::dspac{input, MPI_COMM_WORLD, infinity}; + + { + dspac_first_split_probe::activation const probe; + splitter.construct(split); + } + + require_exact_split(split, fixture, rank, expected); + require_exact_reverse_projection(splitter, split, fixture, rank); + require_collective_protocol(); +} + +TEST_CASE("DSPAC first split uses bounded MPI-3 neighborhood rounds") { + auto rank = -1; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + + constexpr auto infinity = parhip::EdgeWeight{53}; + auto const fixture = path_with_isolate_fixture(size); + auto const expected = path_with_isolate_expected(infinity); + auto input = parhip::parallel_graph_access{MPI_COMM_WORLD}; + auto split = parhip::parallel_graph_access{MPI_COMM_WORLD}; + build_graph(input, fixture, rank); + auto splitter = parhip::dspac{ + input, MPI_COMM_WORLD, infinity, + parhip::mpi::collective_options{.mpi3_round_ceiling = 1, + .force_mpi3 = true}}; + + { + dspac_first_split_probe::activation const probe; + splitter.construct(split); + } + + require_exact_split(split, fixture, rank, expected); + require_collective_protocol(false); + REQUIRE(dspac_first_split_probe::large_count_payloads == 0); + REQUIRE(dspac_first_split_probe::maximum_payload_count <= 1); + if (size >= 2 && size <= 4) { + REQUIRE(dspac_first_split_probe::legacy_payloads > 1); + } +} diff --git a/parallel/parallel_src/tests/dspac/verify_first_split_failure.cmake b/parallel/parallel_src/tests/dspac/verify_first_split_failure.cmake new file mode 100644 index 00000000..2a609717 --- /dev/null +++ b/parallel/parallel_src/tests/dspac/verify_first_split_failure.cmake @@ -0,0 +1,86 @@ +cmake_minimum_required(VERSION 4.0) + +if( + NOT DEFINED MPIEXEC_EXECUTABLE + OR NOT DEFINED MPIEXEC_NUMPROC_FLAG + OR NOT DEFINED PROBE + OR NOT DEFINED MODE +) + message(FATAL_ERROR "MPI launcher, PROBE, and MODE are required") +endif() + +execute_process( + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 2 + ${MPIEXEC_PREFLAGS} + "${PROBE}" "${MODE}" + ${MPIEXEC_POSTFLAGS} + RESULT_VARIABLE probe_result + OUTPUT_VARIABLE probe_stdout + ERROR_VARIABLE probe_stderr + TIMEOUT 8 +) + +set(probe_output "${probe_stdout}\n${probe_stderr}") +if("${probe_result}" STREQUAL "0") + message(FATAL_ERROR "DSPAC first-split failure probe returned success\n${probe_output}") +endif() +if("${probe_result}" MATCHES "[Tt]imeout") + message(FATAL_ERROR "DSPAC first-split failure probe timed out\n${probe_output}") +endif() + +string( + REGEX MATCHALL + "observed DSPAC MPI_Abort on affected communicator" + abort_markers + "${probe_output}" +) +list(LENGTH abort_markers abort_count) +if(NOT abort_count EQUAL 2) + message( + FATAL_ERROR + "expected exactly one DSPAC abort marker per rank; found ${abort_count}\n${probe_output}" + ) +endif() + +if(MODE STREQUAL "neighbor-payload") + set( + expected_diagnostic + "MPI backend failure: MPI_Neighbor_alltoallv(_c)?\\(neighbor exchange\\)" + ) +elseif(MODE STREQUAL "projection-permutation") + set( + expected_diagnostic + "MPI adapter programming failure: DSPAC projection permutation must be a bijection over local edges" + ) +elseif(MODE STREQUAL "projection-barrier") + set( + expected_diagnostic + "MPI backend failure: MPI_Barrier\\(after DSPAC projection\\)" + ) +else() + message(FATAL_ERROR "unknown DSPAC failure mode: ${MODE}") +endif() + +if(NOT probe_output MATCHES "${expected_diagnostic}") + message( + FATAL_ERROR + "missing DSPAC ${MODE} failure diagnostic\n${probe_output}" + ) +endif() + +foreach( + forbidden + IN ITEMS + "unexpected state" + "returned without fail-fast" + "MPI_Finalize" +) + string(FIND "${probe_output}" "${forbidden}" forbidden_offset) + if(NOT forbidden_offset EQUAL -1) + message( + FATAL_ERROR + "DSPAC first-split failure used forbidden path '${forbidden}'\n${probe_output}" + ) + endif() +endforeach() diff --git a/parallel/parallel_src/tests/dspac/verify_vertex_cut_failure.cmake b/parallel/parallel_src/tests/dspac/verify_vertex_cut_failure.cmake new file mode 100644 index 00000000..825844c8 --- /dev/null +++ b/parallel/parallel_src/tests/dspac/verify_vertex_cut_failure.cmake @@ -0,0 +1,75 @@ +cmake_minimum_required(VERSION 4.0) + +if( + NOT DEFINED MPIEXEC_EXECUTABLE + OR NOT DEFINED MPIEXEC_NUMPROC_FLAG + OR NOT DEFINED PROBE + OR NOT DEFINED MODE + OR NOT DEFINED EXPECTED_DIAGNOSTIC +) + message( + FATAL_ERROR + "MPI launcher, PROBE, MODE, and EXPECTED_DIAGNOSTIC are required" + ) +endif() + +execute_process( + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 2 + ${MPIEXEC_PREFLAGS} + "${PROBE}" + ${MPIEXEC_POSTFLAGS} + "${MODE}" + RESULT_VARIABLE probe_result + OUTPUT_VARIABLE probe_stdout + ERROR_VARIABLE probe_stderr + TIMEOUT 8 +) + +set(probe_output "${probe_stdout}\n${probe_stderr}") +if("${probe_result}" STREQUAL "0") + message(FATAL_ERROR "failure probe returned success\n${probe_output}") +endif() +if("${probe_result}" MATCHES "[Tt]imeout") + message(FATAL_ERROR "failure probe timed out\n${probe_output}") +endif() + +string( + REGEX MATCHALL + "observed vertex-cut MPI_Abort on affected communicator" + abort_markers + "${probe_output}" +) +list(LENGTH abort_markers abort_count) +if(NOT abort_count EQUAL 2) + message( + FATAL_ERROR + "expected exactly 2 affected-communicator abort markers; found ${abort_count}\n${probe_output}" + ) +endif() + +string( + FIND + "${probe_output}" + "${EXPECTED_DIAGNOSTIC}" + diagnostic_offset +) +if(diagnostic_offset EQUAL -1) + message( + FATAL_ERROR + "missing vertex-cut failure diagnostic '${EXPECTED_DIAGNOSTIC}'\n${probe_output}" + ) +endif() + +foreach( + forbidden + IN ITEMS + "unexpected state" + "returned without fail-fast" + "MPI_Finalize" +) + string(FIND "${probe_output}" "${forbidden}" forbidden_offset) + if(NOT forbidden_offset EQUAL -1) + message(FATAL_ERROR "failure probe used ${forbidden}\n${probe_output}") + endif() +endforeach() diff --git a/parallel/parallel_src/tests/dspac/vertex_cut_failure_probe.cpp b/parallel/parallel_src/tests/dspac/vertex_cut_failure_probe.cpp new file mode 100644 index 00000000..3f383aba --- /dev/null +++ b/parallel/parallel_src/tests/dspac/vertex_cut_failure_probe.cpp @@ -0,0 +1,291 @@ +#include +#include + +#include +#include +#include +#include + +#include "communication/mpi_fixed_reduction.h" +#include "data_structure/parallel_graph_access.h" +#include "definitions.h" +#include "dspac/dspac.h" + +// KAHIP_PMPI_CALLBACK_REGION_BEGIN +namespace vertex_cut_failure_probe { +enum class mode : unsigned char { + backend, + zero_k, + undersized_partition, + out_of_range_label, + mismatched_k, + intercommunicator, +}; + +inline bool active = false; +inline mode selected = mode::backend; +inline MPI_Comm expected_communicator = MPI_COMM_NULL; +inline int all_reductions = 0; +inline int minima = 0; +inline int maxima = 0; +inline int validations = 0; +inline int sums = 0; +inline int finalizations = 0; +inline bool callback_error = false; + +void write_text(std::string_view text) noexcept { + static_cast(::write(STDERR_FILENO, text.data(), text.size())); +} + +[[nodiscard]] auto valid_all_reduce(void const* send_buffer, + void* receive_buffer, + int count, + MPI_Datatype datatype, + MPI_Op operation, + MPI_Comm communicator) noexcept -> bool { + auto const supported_operation = operation == MPI_MIN || + operation == MPI_MAX || + operation == MPI_BOR || operation == MPI_SUM; + return send_buffer != nullptr && receive_buffer != nullptr && + send_buffer != receive_buffer && count == 1 && + datatype == MPI_UNSIGNED_LONG_LONG && supported_operation && + communicator == expected_communicator; +} + +[[nodiscard]] auto expected_abort_state() noexcept -> bool { + if (callback_error || finalizations != 0) { + return false; + } + switch (selected) { + case mode::backend: + return all_reductions == 4 && minima == 1 && maxima == 1 && + validations == 1 && sums == 1; + case mode::zero_k: + case mode::undersized_partition: + case mode::out_of_range_label: + case mode::mismatched_k: + return all_reductions == 3 && minima == 1 && maxima == 1 && + validations == 1 && sums == 0; + case mode::intercommunicator: + return all_reductions == 0 && minima == 0 && maxima == 0 && + validations == 0 && sums == 0; + } + return false; +} + +[[noreturn]] void observed_abort(MPI_Comm communicator, + int error_code) noexcept { + int relation = MPI_UNEQUAL; + if (error_code != EXIT_FAILURE || + PMPI_Comm_compare(communicator, expected_communicator, &relation) != + MPI_SUCCESS || + relation != MPI_IDENT || !expected_abort_state()) { + write_text("observed vertex-cut MPI_Abort with unexpected state\n"); + std::_Exit(91); + } + write_text("observed vertex-cut MPI_Abort on affected communicator\n"); + if (PMPI_Barrier(MPI_COMM_WORLD) != MPI_SUCCESS) { + std::_Exit(90); + } + std::_Exit(86); +} +} // namespace vertex_cut_failure_probe + +static_assert(noexcept(vertex_cut_failure_probe::write_text({}))); +static_assert( + noexcept(vertex_cut_failure_probe::valid_all_reduce(nullptr, + nullptr, + 0, + MPI_DATATYPE_NULL, + MPI_OP_NULL, + MPI_COMM_NULL))); +static_assert(noexcept(vertex_cut_failure_probe::expected_abort_state())); +static_assert(noexcept(vertex_cut_failure_probe::observed_abort(MPI_COMM_NULL, + 0))); + +extern "C" int MPI_Allreduce(void const* send_buffer, + void* receive_buffer, + int count, + MPI_Datatype datatype, + MPI_Op operation, + MPI_Comm communicator) { + using namespace vertex_cut_failure_probe; + if (!active) { + return PMPI_Allreduce(send_buffer, receive_buffer, count, datatype, + operation, communicator); + } + + ++all_reductions; + if (!valid_all_reduce(send_buffer, receive_buffer, count, datatype, operation, + communicator)) { + callback_error = true; + return MPI_ERR_OTHER; + } + if (operation == MPI_MIN) { + ++minima; + } else if (operation == MPI_MAX) { + ++maxima; + } else if (operation == MPI_BOR) { + ++validations; + } else { + ++sums; + if (selected != mode::backend) { + callback_error = true; + } + return MPI_ERR_OTHER; + } + return PMPI_Allreduce(send_buffer, receive_buffer, count, datatype, operation, + communicator); +} + +extern "C" int MPI_Finalize() { + if (vertex_cut_failure_probe::active) { + ++vertex_cut_failure_probe::finalizations; + return MPI_SUCCESS; + } + return PMPI_Finalize(); +} + +extern "C" int MPI_Abort(MPI_Comm communicator, int error_code) { + vertex_cut_failure_probe::observed_abort(communicator, error_code); +} +// KAHIP_PMPI_CALLBACK_REGION_END + +namespace { +[[nodiscard]] auto parse_mode(std::string_view value) + -> vertex_cut_failure_probe::mode { + using mode = vertex_cut_failure_probe::mode; + if (value == "backend") { + return mode::backend; + } + if (value == "zero-k") { + return mode::zero_k; + } + if (value == "undersized-partition") { + return mode::undersized_partition; + } + if (value == "out-of-range-label") { + return mode::out_of_range_label; + } + if (value == "mismatched-k") { + return mode::mismatched_k; + } + if (value == "intercommunicator") { + return mode::intercommunicator; + } + vertex_cut_failure_probe::write_text("unknown vertex-cut probe mode\n"); + std::_Exit(2); +} + +void build_vertex_cut_fixture(parhip::parallel_graph_access& graph, + int rank, + int size, + std::vector& partition) { + constexpr auto local_edges = parhip::EdgeID{3}; + auto const global_nodes = static_cast(size); + auto const global_edges = static_cast(size) * local_edges; + graph.start_construction(1, local_edges, global_nodes, global_edges, false); + + auto ranges = std::vector(static_cast(size) + 1); + for (int index = 0; index <= size; ++index) { + ranges[static_cast(index)] = + static_cast(index); + } + graph.set_range(static_cast(rank), + static_cast(rank)); + graph.set_range_array(ranges); + + auto const node = graph.new_node(); + graph.setNodeWeight(node, 1); + partition = {0, 1, 2}; + for ([[maybe_unused]] auto const block : partition) { + auto const edge = graph.new_edge(node, static_cast(rank)); + graph.setEdgeWeight(edge, 1); + } + graph.finish_construction(); +} + +[[nodiscard]] auto make_intercommunicator(int world_rank) -> MPI_Comm { + auto local = MPI_COMM_NULL; + if (PMPI_Comm_split(MPI_COMM_WORLD, world_rank, 0, &local) != MPI_SUCCESS || + local == MPI_COMM_NULL) { + std::_Exit(7); + } + auto intercommunicator = MPI_COMM_NULL; + if (PMPI_Intercomm_create(local, 0, MPI_COMM_WORLD, 1 - world_rank, 731, + &intercommunicator) != MPI_SUCCESS || + intercommunicator == MPI_COMM_NULL) { + std::_Exit(8); + } + if (PMPI_Comm_free(&local) != MPI_SUCCESS) { + std::_Exit(9); + } + return intercommunicator; +} +} // namespace + +int main(int argc, char** argv) { + if (argc != 2 || MPI_Init(&argc, &argv) != MPI_SUCCESS) { + return 2; + } + + auto world_rank = -1; + auto world_size = 0; + if (MPI_Comm_rank(MPI_COMM_WORLD, &world_rank) != MPI_SUCCESS || + MPI_Comm_size(MPI_COMM_WORLD, &world_size) != MPI_SUCCESS || + world_size != 2) { + return 3; + } + + using mode = vertex_cut_failure_probe::mode; + auto const selected = parse_mode(argv[1]); + auto communicator = MPI_COMM_NULL; + if (selected == mode::intercommunicator) { + communicator = make_intercommunicator(world_rank); + } else if (MPI_Comm_split(MPI_COMM_WORLD, 0, world_size - world_rank, + &communicator) != MPI_SUCCESS || + communicator == MPI_COMM_NULL) { + return 4; + } + if (MPI_Comm_set_errhandler(communicator, MPI_ERRORS_RETURN) != MPI_SUCCESS) { + return 5; + } + + auto rank = -1; + auto size = 0; + if (MPI_Comm_rank(communicator, &rank) != MPI_SUCCESS || + MPI_Comm_size(communicator, &size) != MPI_SUCCESS) { + return 6; + } + + vertex_cut_failure_probe::selected = selected; + vertex_cut_failure_probe::expected_communicator = communicator; + vertex_cut_failure_probe::active = true; + + if (selected == mode::intercommunicator) { + static_cast(parhip::mpi::all_reduce_sum( + parhip::EdgeWeight{1}, parhip::mpi::communicator_view{communicator}, + "MPI_Allreduce(vertex cut intercommunicator probe)")); + } else { + auto graph = parhip::parallel_graph_access{communicator}; + auto partition = std::vector{}; + build_vertex_cut_fixture(graph, rank, size, partition); + if (selected == mode::undersized_partition) { + partition.pop_back(); + } else if (selected == mode::out_of_range_label) { + partition.back() = 3; + } + + auto splitter = parhip::dspac{ + graph, communicator, std::numeric_limits::max()}; + auto const k = selected == mode::zero_k ? parhip::PartitionID{0} + : selected == mode::mismatched_k + ? static_cast(rank + 3) + : parhip::PartitionID{3}; + static_cast(splitter.calculate_vertex_cut(k, partition)); + } + + vertex_cut_failure_probe::write_text( + "vertex-cut operation returned without fail-fast\n"); + std::_Exit(92); +} diff --git a/parallel/parallel_src/tests/dspac/vertex_cut_mpi_test.cpp b/parallel/parallel_src/tests/dspac/vertex_cut_mpi_test.cpp new file mode 100644 index 00000000..b7dfe7ad --- /dev/null +++ b/parallel/parallel_src/tests/dspac/vertex_cut_mpi_test.cpp @@ -0,0 +1,246 @@ +#include + +#include + +#include +#include +#include +#include + +#include "communication/mpi_fixed_reduction.h" +#include "data_structure/parallel_graph_access.h" +#include "definitions.h" +#include "dspac/dspac.h" + +// KAHIP_PMPI_CALLBACK_REGION_BEGIN +namespace vertex_cut_probe { +struct observation final { + int count = 0; + MPI_Datatype datatype = MPI_DATATYPE_NULL; + MPI_Op operation = MPI_OP_NULL; + MPI_Comm communicator = MPI_COMM_NULL; + parhip::EdgeWeight local_value = 0; + bool buffers_are_distinct = false; +}; + +inline bool active = false; +inline int call_count = 0; +inline int minimum_count = 0; +inline int maximum_count = 0; +inline int validation_count = 0; +inline bool unexpected_operation = false; +inline bool all_calls_well_formed = true; +inline MPI_Comm expected_communicator = MPI_COMM_NULL; +inline observation observed{}; + +void reset(MPI_Comm communicator) noexcept { + call_count = 0; + minimum_count = 0; + maximum_count = 0; + validation_count = 0; + unexpected_operation = false; + all_calls_well_formed = true; + expected_communicator = communicator; + observed = {}; +} + +void record(void const* send_buffer, + void* receive_buffer, + int count, + MPI_Datatype datatype, + MPI_Op operation, + MPI_Comm communicator) noexcept { + if (!active) { + return; + } + ++call_count; + all_calls_well_formed = all_calls_well_formed && send_buffer != nullptr && + receive_buffer != nullptr && + send_buffer != receive_buffer && count == 1 && + datatype == MPI_UNSIGNED_LONG_LONG && + communicator == expected_communicator; + if (operation == MPI_MIN) { + ++minimum_count; + return; + } + if (operation == MPI_MAX) { + ++maximum_count; + return; + } + if (operation == MPI_BOR) { + ++validation_count; + return; + } + if (operation != MPI_SUM) { + unexpected_operation = true; + return; + } + observed = { + .count = count, + .datatype = datatype, + .operation = operation, + .communicator = communicator, + .local_value = send_buffer == nullptr + ? parhip::EdgeWeight{} + : *static_cast(send_buffer), + .buffers_are_distinct = send_buffer != nullptr && + receive_buffer != nullptr && + send_buffer != receive_buffer, + }; +} + +class activation final { + public: + explicit activation(MPI_Comm communicator) noexcept { + reset(communicator); + active = true; + } + + ~activation() noexcept { active = false; } + + activation(activation const&) = delete; + auto operator=(activation const&) -> activation& = delete; +}; +} // namespace vertex_cut_probe + +static_assert(noexcept(vertex_cut_probe::reset(MPI_COMM_NULL))); +static_assert(noexcept(vertex_cut_probe::record(nullptr, + nullptr, + 0, + MPI_DATATYPE_NULL, + MPI_OP_NULL, + MPI_COMM_NULL))); + +extern "C" int MPI_Allreduce(void const* send_buffer, + void* receive_buffer, + int count, + MPI_Datatype datatype, + MPI_Op operation, + MPI_Comm communicator) { + vertex_cut_probe::record(send_buffer, receive_buffer, count, datatype, + operation, communicator); + return PMPI_Allreduce(send_buffer, receive_buffer, count, datatype, operation, + communicator); +} +// KAHIP_PMPI_CALLBACK_REGION_END + +namespace { +template +concept fixed_sum_reducible = + requires(T value, parhip::mpi::communicator_view communicator) { + parhip::mpi::all_reduce_sum(value, communicator, "test reduction"); + }; + +static_assert(fixed_sum_reducible); +static_assert(!fixed_sum_reducible); + +void build_vertex_cut_fixture(parhip::parallel_graph_access& graph, + int rank, + int size, + std::vector& partition) { + constexpr auto labels_by_node = std::array{ + std::array{0, 0, 1}, + std::array{0, 1, 1}, + std::array{2, 2, 2}, + }; + constexpr auto nodes_per_active_rank = parhip::NodeID{3}; + constexpr auto edges_per_active_rank = parhip::EdgeID{9}; + auto const local_nodes = rank == 0 ? parhip::NodeID{} : nodes_per_active_rank; + auto const local_edges = rank == 0 ? parhip::EdgeID{} : edges_per_active_rank; + auto const global_nodes = + static_cast(size - 1) * nodes_per_active_rank; + auto const global_edges = + static_cast(size - 1) * edges_per_active_rank; + + graph.start_construction(local_nodes, local_edges, global_nodes, global_edges, + false); + auto ranges = std::vector(static_cast(size) + 1); + for (int index = 0; index <= size; ++index) { + ranges[static_cast(index)] = + index == 0 + ? parhip::NodeID{} + : static_cast(index - 1) * nodes_per_active_rank; + } + auto const from = ranges[static_cast(rank)]; + auto const to = + local_nodes == 0 ? from : ranges[static_cast(rank) + 1] - 1; + graph.set_range(from, to); + graph.set_range_array(ranges); + + if (rank != 0) { + for (auto const& labels : labels_by_node) { + auto const node = graph.new_node(); + graph.setNodeWeight(node, 1); + for (auto const block : labels) { + partition.push_back(block); + auto const edge = graph.new_edge(node, from + node); + graph.setEdgeWeight(edge, 1); + } + } + } + graph.finish_construction(); +} +} // namespace + +TEST_CASE("vertex cut is summed exactly onto every rank") { + auto world_rank = -1; + auto world_size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &world_rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &world_size) == MPI_SUCCESS); + REQUIRE(world_size >= 1); + REQUIRE(world_size <= 5); + + auto communicator = MPI_COMM_NULL; + REQUIRE(MPI_Comm_split(MPI_COMM_WORLD, 0, world_size - world_rank, + &communicator) == MPI_SUCCESS); + REQUIRE(communicator != MPI_COMM_NULL); + + auto rank = -1; + REQUIRE(MPI_Comm_rank(communicator, &rank) == MPI_SUCCESS); + + constexpr auto expected_by_size = + std::array{0, 2, 4, 6, 8}; + auto result = parhip::EdgeWeight{}; + auto observed = vertex_cut_probe::observation{}; + auto operation_count = 0; + auto minimum_count = 0; + auto maximum_count = 0; + auto validation_count = 0; + auto unexpected_operation = false; + auto all_calls_well_formed = false; + { + auto graph = parhip::parallel_graph_access{communicator}; + auto partition = std::vector{}; + build_vertex_cut_fixture(graph, rank, world_size, partition); + auto splitter = parhip::dspac{ + graph, communicator, std::numeric_limits::max()}; + + { + vertex_cut_probe::activation const probe{communicator}; + result = splitter.calculate_vertex_cut(3, partition); + observed = vertex_cut_probe::observed; + operation_count = vertex_cut_probe::call_count; + minimum_count = vertex_cut_probe::minimum_count; + maximum_count = vertex_cut_probe::maximum_count; + validation_count = vertex_cut_probe::validation_count; + unexpected_operation = vertex_cut_probe::unexpected_operation; + all_calls_well_formed = vertex_cut_probe::all_calls_well_formed; + } + } + + REQUIRE(result == expected_by_size[static_cast(world_size - 1)]); + REQUIRE(operation_count == 4); + REQUIRE(minimum_count == 1); + REQUIRE(maximum_count == 1); + REQUIRE(validation_count == 1); + REQUIRE_FALSE(unexpected_operation); + REQUIRE(all_calls_well_formed); + REQUIRE(observed.count == 1); + REQUIRE(observed.datatype == MPI_UNSIGNED_LONG_LONG); + REQUIRE(observed.operation == MPI_SUM); + REQUIRE(observed.communicator == communicator); + REQUIRE(observed.local_value == (rank == 0 ? 0 : 2)); + REQUIRE(observed.buffers_are_distinct); + + REQUIRE(MPI_Comm_free(&communicator) == MPI_SUCCESS); +} diff --git a/parallel/parallel_src/tests/evolutionary/evolutionary_failure_probe.cpp b/parallel/parallel_src/tests/evolutionary/evolutionary_failure_probe.cpp new file mode 100644 index 00000000..70cffe2c --- /dev/null +++ b/parallel/parallel_src/tests/evolutionary/evolutionary_failure_probe.cpp @@ -0,0 +1,489 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "configuration.h" +#include "data_structure/graph_access.h" +#include "parallel_mh/exchange/exchanger.h" +#include "parallel_mh/parallel_mh_async.h" +#include "parallel_mh/population.h" +#include "tools/random_functions.h" +#include "kaHIP_evolutionary_interface_internal.h" + +// KAHIP_PMPI_CALLBACK_REGION_BEGIN +namespace evolutionary_failure_probe { +enum class mode : unsigned char { + communicator_duplication, + sendrecv, + isend, + wrong_tag, + wrong_count, + wait, + combine_cross_conditional, + invalid_label, + weight_overflow, + upper_bound_narrowing, +}; + +inline bool active = false; +inline mode selected = mode::communicator_duplication; +inline MPI_Comm expected_communicator = MPI_COMM_NULL; +inline int duplications = 0; +inline int sendrecvs = 0; +inline int isends = 0; +inline int tests = 0; +inline int waits = 0; +inline int receives = 0; +inline int cancellations = 0; +inline int finalizations = 0; +inline int communicator_size_queries = 0; +inline int broadcasts = 0; +inline bool callback_error = false; + +[[nodiscard]] auto feasibility_failure(mode value) noexcept -> bool { + return value == mode::invalid_label || value == mode::weight_overflow; +} + +void write_text(std::string_view text) noexcept { + static_cast(::write(STDERR_FILENO, text.data(), text.size())); +} + +[[nodiscard]] auto expected_abort_state() noexcept -> bool { + if (callback_error || finalizations != 0 || cancellations != 0) { + return false; + } + switch (selected) { + case mode::communicator_duplication: + return duplications == 1; + case mode::sendrecv: + return sendrecvs == 1; + case mode::isend: + return isends == 1; + case mode::wrong_tag: + case mode::wrong_count: + return receives == 0; + case mode::wait: + return isends == 1 && tests >= 1 && waits == 1; + case mode::combine_cross_conditional: + return communicator_size_queries == 1 && broadcasts == 0; + case mode::invalid_label: + case mode::weight_overflow: + return duplications == 1 && broadcasts == 0; + case mode::upper_bound_narrowing: + return duplications == 0 && broadcasts == 0; + } + return false; +} + +[[noreturn]] void observed_abort(MPI_Comm communicator, + int error_code) noexcept { + auto relation = int{MPI_UNEQUAL}; + if (expected_communicator == MPI_COMM_NULL || error_code != EXIT_FAILURE || + PMPI_Comm_compare(communicator, expected_communicator, &relation) != + MPI_SUCCESS || + relation != MPI_IDENT || !expected_abort_state()) { + write_text( + "observed evolutionary lifetime MPI_Abort with unexpected state\n"); + std::_Exit(91); + } + write_text( + "observed evolutionary lifetime MPI_Abort on affected communicator\n"); + if (PMPI_Barrier(MPI_COMM_WORLD) != MPI_SUCCESS) { + std::_Exit(90); + } + std::_Exit(86); +} +} // namespace evolutionary_failure_probe + +static_assert(noexcept(evolutionary_failure_probe::write_text({}))); +static_assert(noexcept(evolutionary_failure_probe::expected_abort_state())); +static_assert(noexcept(evolutionary_failure_probe::observed_abort(MPI_COMM_NULL, + 0))); + +extern "C" int MPI_Comm_dup(MPI_Comm communicator, MPI_Comm* duplicate) { + using namespace evolutionary_failure_probe; + if (!active) { + return PMPI_Comm_dup(communicator, duplicate); + } + ++duplications; + if (feasibility_failure(selected)) { + if (communicator != expected_communicator || duplicate == nullptr) { + callback_error = true; + return MPI_ERR_OTHER; + } + auto const result = PMPI_Comm_dup(communicator, duplicate); + if (result == MPI_SUCCESS) { + expected_communicator = *duplicate; + } + return result; + } + if (selected != mode::communicator_duplication || + communicator != expected_communicator || duplicate == nullptr) { + callback_error = true; + } + return MPI_ERR_OTHER; +} + +extern "C" int MPI_Bcast(void* buffer, + int count, + MPI_Datatype datatype, + int root, + MPI_Comm communicator) { + using namespace evolutionary_failure_probe; + if (active && feasibility_failure(selected)) { + ++broadcasts; + callback_error = true; + } + if (active && selected == mode::combine_cross_conditional) { + ++broadcasts; + callback_error = true; + return MPI_ERR_OTHER; + } + return PMPI_Bcast(buffer, count, datatype, root, communicator); +} + +extern "C" int MPI_Comm_size(MPI_Comm communicator, int* size) { + using namespace evolutionary_failure_probe; + if (active && selected == mode::combine_cross_conditional) { + ++communicator_size_queries; + if (communicator != expected_communicator || size == nullptr) { + callback_error = true; + return MPI_ERR_OTHER; + } + } + return PMPI_Comm_size(communicator, size); +} + +extern "C" int MPI_Sendrecv(void const* send_buffer, + int send_count, + MPI_Datatype send_datatype, + int destination, + int send_tag, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_datatype, + int source, + int receive_tag, + MPI_Comm communicator, + MPI_Status* status) { + using namespace evolutionary_failure_probe; + if (!active) { + return PMPI_Sendrecv(send_buffer, send_count, send_datatype, destination, + send_tag, receive_buffer, receive_count, + receive_datatype, source, receive_tag, communicator, + status); + } + ++sendrecvs; + if (selected != mode::sendrecv || communicator != expected_communicator || + send_buffer == nullptr || receive_buffer == nullptr || send_count != 8 || + receive_count != 8 || send_datatype != MPI_INT || + receive_datatype != MPI_INT || send_tag != 0 || receive_tag != 0 || + destination < 0 || source < 0 || status == nullptr) { + callback_error = true; + } + return MPI_ERR_OTHER; +} + +extern "C" int MPI_Isend(void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator, + MPI_Request* request) { + using namespace evolutionary_failure_probe; + if (!active) { + return PMPI_Isend(buffer, count, datatype, destination, tag, communicator, + request); + } + ++isends; + if ((selected != mode::isend && selected != mode::wait) || + communicator != expected_communicator || buffer == nullptr || + count != 8 || datatype != MPI_INT || destination < 0 || + tag != destination || request == nullptr) { + callback_error = true; + } + if (selected == mode::isend) { + return MPI_ERR_OTHER; + } + return PMPI_Isend(buffer, count, datatype, destination, tag, communicator, + request); +} + +extern "C" int MPI_Test(MPI_Request* request, + int* completed, + MPI_Status* status) { + using namespace evolutionary_failure_probe; + if (!active) { + return PMPI_Test(request, completed, status); + } + ++tests; + if (selected != mode::wait || request == nullptr || completed == nullptr) { + callback_error = true; + return MPI_ERR_OTHER; + } + *completed = 0; + return MPI_SUCCESS; +} + +extern "C" int MPI_Wait(MPI_Request* request, MPI_Status* status) { + using namespace evolutionary_failure_probe; + if (!active) { + return PMPI_Wait(request, status); + } + ++waits; + if (selected != mode::wait || request == nullptr) { + callback_error = true; + } + return MPI_ERR_OTHER; +} + +extern "C" int MPI_Recv(void* buffer, + int count, + MPI_Datatype datatype, + int source, + int tag, + MPI_Comm communicator, + MPI_Status* status) { + if (evolutionary_failure_probe::active) { + ++evolutionary_failure_probe::receives; + } + return PMPI_Recv(buffer, count, datatype, source, tag, communicator, status); +} + +extern "C" int MPI_Cancel(MPI_Request* request) { + if (evolutionary_failure_probe::active) { + ++evolutionary_failure_probe::cancellations; + } + return PMPI_Cancel(request); +} + +extern "C" int MPI_Finalize() { + if (evolutionary_failure_probe::active) { + ++evolutionary_failure_probe::finalizations; + return MPI_SUCCESS; + } + return PMPI_Finalize(); +} + +extern "C" int MPI_Abort(MPI_Comm communicator, int error_code) { + evolutionary_failure_probe::observed_abort(communicator, error_code); +} +// KAHIP_PMPI_CALLBACK_REGION_END + +namespace { +[[nodiscard]] auto parse_mode(std::string_view value) + -> evolutionary_failure_probe::mode { + using mode = evolutionary_failure_probe::mode; + if (value == "communicator-duplication") { + return mode::communicator_duplication; + } + if (value == "sendrecv") + return mode::sendrecv; + if (value == "isend") + return mode::isend; + if (value == "wrong-tag") + return mode::wrong_tag; + if (value == "wrong-count") + return mode::wrong_count; + if (value == "wait") + return mode::wait; + if (value == "combine-cross-conditional") + return mode::combine_cross_conditional; + if (value == "invalid-label") + return mode::invalid_label; + if (value == "weight-overflow") + return mode::weight_overflow; + if (value == "upper-bound-narrowing") + return mode::upper_bound_narrowing; + evolutionary_failure_probe::write_text( + "unknown evolutionary lifetime failure mode\n"); + std::_Exit(2); +} + +void build_cycle_graph(kahip::modified::graph_access& graph, int rank) { + constexpr auto nodes = kahip::modified::NodeID{8}; + graph.start_construction(nodes, 2 * nodes); + graph.set_partition_count(2); + for (auto node = kahip::modified::NodeID{0}; node < nodes; ++node) { + auto const created = graph.new_node(); + graph.setNodeWeight(created, 1); + graph.setPartitionIndex(created, + static_cast( + (node + static_cast(rank)) % 2)); + for (auto const target : + std::array{(node + nodes - 1) % nodes, (node + 1) % nodes}) { + auto const edge = graph.new_edge(node, target); + graph.setEdgeWeight(edge, 1); + } + } + graph.finish_construction(); +} + +[[nodiscard]] auto make_config() -> kahip::modified::PartitionConfig { + auto config = kahip::modified::PartitionConfig{}; + config.k = 2; + auto defaults = kahip::modified::configuration{}; + defaults.standard(config); + config.mh_pool_size = 64; + config.mh_num_ncs_to_compute = 0; + config.mh_optimize_communication_volume = false; + config.mh_penalty_for_unconnected = false; + config.largest_graph_weight = 8; + config.upper_bound_partition = 8; + return config; +} + +[[nodiscard]] auto make_individual(kahip::modified::graph_access& graph, + int rank) -> kahip::modified::Individuum { + auto result = kahip::modified::Individuum{ + .partition_map = new int[graph.number_of_nodes()], + .objective = rank + 1, + .cut_edges = new std::vector{}, + }; + for (auto node = kahip::modified::NodeID{0}; node < graph.number_of_nodes(); + ++node) { + result.partition_map[node] = graph.getPartitionIndex(node); + } + for (auto node = kahip::modified::NodeID{0}; node < graph.number_of_nodes(); + ++node) { + for (auto edge = graph.get_first_edge(node); + edge < graph.get_first_invalid_edge(node); ++edge) { + if (result.partition_map[node] != + result.partition_map[graph.getEdgeTarget(edge)]) { + result.cut_edges->push_back(edge); + } + } + } + return result; +} +} // namespace + +int main(int argc, char** argv) { + if (argc != 2 || MPI_Init(&argc, &argv) != MPI_SUCCESS) { + return 2; + } + auto world_rank = -1; + auto world_size = 0; + if (MPI_Comm_rank(MPI_COMM_WORLD, &world_rank) != MPI_SUCCESS || + MPI_Comm_size(MPI_COMM_WORLD, &world_size) != MPI_SUCCESS || + world_size != 2) { + return 3; + } + auto communicator = MPI_COMM_NULL; + if (MPI_Comm_split(MPI_COMM_WORLD, 0, 1 - world_rank, &communicator) != + MPI_SUCCESS || + communicator == MPI_COMM_NULL || + MPI_Comm_set_errhandler(communicator, MPI_ERRORS_RETURN) != MPI_SUCCESS) { + return 4; + } + auto rank = -1; + if (MPI_Comm_rank(communicator, &rank) != MPI_SUCCESS) { + return 5; + } + + auto const selected = parse_mode(argv[1]); + evolutionary_failure_probe::selected = selected; + evolutionary_failure_probe::expected_communicator = communicator; + if (selected == evolutionary_failure_probe::mode::communicator_duplication) { + evolutionary_failure_probe::active = true; + auto driver = kahip::modified::parallel_mh_async{communicator}; + static_cast(driver); + } + + if (selected == evolutionary_failure_probe::mode::upper_bound_narrowing) { + auto n = 1; + auto offsets = std::array{0, 0}; + auto blocks = 1; + auto edge_cut = -1; + auto balance = 0.0; + auto partition = 0; + evolutionary_failure_probe::active = true; + kahip::modified::kaffpaE_with_upper_bound( + &n, nullptr, offsets.data(), nullptr, nullptr, &blocks, true, false, + 0, 1, 0, communicator, 0U, + std::numeric_limits::max(), &edge_cut, &balance, + &partition); + } + + auto graph = kahip::modified::graph_access{}; + build_cycle_graph(graph, rank); + auto config = make_config(); + auto island = kahip::modified::population{communicator, config}; + auto initial = make_individual(graph, rank); + island.insert(graph, initial); + kahip::modified::random_functions::setSeed(71 + rank); + using mode = evolutionary_failure_probe::mode; + + if (selected == mode::invalid_label || selected == mode::weight_overflow) { + if (selected == mode::invalid_label) { + graph.setPartitionIndex(0, config.k); + } else { + for (auto node = kahip::modified::NodeID{0}; + node < graph.number_of_nodes(); ++node) { + graph.setPartitionIndex(node, kahip::modified::PartitionID{0}); + graph.setNodeWeight(node, kahip::modified::NodeWeight{0}); + } + graph.setNodeWeight( + 0, std::numeric_limits::max()); + graph.setNodeWeight(1, kahip::modified::NodeWeight{1}); + } + evolutionary_failure_probe::active = true; + auto driver = kahip::modified::parallel_mh_async{communicator}; + static_cast(driver.collect_best_partitioning( + graph, config, + static_cast(rank + 1))); + } + + if (selected == mode::combine_cross_conditional) { + // Model asynchronous entry: only one process calls combine_cross. The peer + // waits in the PMPI harness solely so the intercepted abort can emit one + // deterministic marker without replacing the real MPI_Abort semantics. + if (rank != 0) { + static_cast(PMPI_Barrier(MPI_COMM_WORLD)); + std::_Exit(85); + } + config.mh_cross_combine_original_k = true; + auto output = kahip::modified::Individuum{}; + evolutionary_failure_probe::active = true; + island.combine_cross(config, graph, initial, output); + } + + auto exchange = kahip::modified::exchanger{communicator}; + if (selected == mode::wrong_tag || selected == mode::wrong_count) { + auto payload = std::vector(selected == mode::wrong_count + ? graph.number_of_nodes() + 1 + : graph.number_of_nodes(), + rank); + auto request = MPI_REQUEST_NULL; + auto const destination = 1 - rank; + auto const tag = selected == mode::wrong_tag ? 947 : destination; + if (PMPI_Isend(payload.data(), static_cast(payload.size()), MPI_INT, + destination, tag, communicator, &request) != MPI_SUCCESS || + PMPI_Barrier(communicator) != MPI_SUCCESS) { + return 6; + } + evolutionary_failure_probe::active = true; + exchange.recv_incoming(config, graph, island); + } else if (selected == mode::sendrecv) { + evolutionary_failure_probe::active = true; + exchange.diversify_population(config, graph, island, false); + } else if (selected == mode::isend) { + evolutionary_failure_probe::active = true; + exchange.push_best(config, graph, island); + } else if (selected == mode::wait) { + evolutionary_failure_probe::active = true; + exchange.push_best(config, graph, island); + exchange.finish(static_cast(graph.number_of_nodes())); + } + + evolutionary_failure_probe::write_text( + "evolutionary lifetime operation returned without fail-fast\n"); + std::_Exit(92); +} diff --git a/parallel/parallel_src/tests/evolutionary/evolutionary_mpi_test.cpp b/parallel/parallel_src/tests/evolutionary/evolutionary_mpi_test.cpp new file mode 100644 index 00000000..44cd30db --- /dev/null +++ b/parallel/parallel_src/tests/evolutionary/evolutionary_mpi_test.cpp @@ -0,0 +1,691 @@ +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "configuration.h" +#include "data_structure/graph_access.h" +#include "parallel_mh/exchange/exchanger.h" +#include "parallel_mh/parallel_mh_async.h" +#include "parallel_mh/evolutionary_feasibility.h" +#include "kaHIP_interface.h" +#include "kaHIP_evolutionary_interface_internal.h" +#include "parallel_mh/population.h" +#include "partition/initial_partitioning/initial_partitioning.h" +#include "partition/uncoarsening/refinement/quotient_graph_refinement/2way_fm_refinement/partition_accept_rule.h" +#include "tools/quality_metrics.h" +#include "tools/random_functions.h" + +// KAHIP_PMPI_CALLBACK_REGION_BEGIN +namespace evolutionary_lifetime_probe { +enum class phase : unsigned char { inactive, communicator, exchange }; + +struct send_record final { + MPI_Request request = MPI_REQUEST_NULL; + int const* buffer = nullptr; + int count = 0; + int destination = -1; + std::uint64_t checksum = 0; + bool completed = false; +}; + +struct counters final { + int duplications = 0; + int error_handler_sets = 0; + int frees = 0; + int isends = 0; + int tests = 0; + int waits = 0; + int cancellations = 0; + int sendrecvs = 0; + int receives = 0; + int unfinished = 0; + bool invalid_call = false; + bool buffer_changed_before_completion = false; + bool repeated_destination = false; +}; + +inline constexpr auto maximum_sends = std::size_t{64}; +inline phase active_phase = phase::inactive; +inline MPI_Comm source_communicator = MPI_COMM_NULL; +inline MPI_Comm duplicated_communicator = MPI_COMM_NULL; +inline int expected_count = 0; +inline int expected_rank = -1; +inline counters observed{}; +inline std::array sends{}; + +[[nodiscard]] auto checksum(int const* values, int count) noexcept + -> std::uint64_t { + auto result = std::uint64_t{1469598103934665603ULL}; + if (values == nullptr || count < 0) { + return 0; + } + for (auto index = 0; index < count; ++index) { + result ^= + static_cast(static_cast(values[index])); + result *= std::uint64_t{1099511628211ULL}; + } + return result; +} + +void reset(phase next, + MPI_Comm communicator, + int rank, + int partition_count) noexcept { + active_phase = next; + source_communicator = communicator; + duplicated_communicator = MPI_COMM_NULL; + expected_count = partition_count; + expected_rank = rank; + observed = {}; + sends = {}; +} + +void record_send(MPI_Request request, + int const* buffer, + int count, + int destination) noexcept { + if (observed.isends <= 0 || + static_cast(observed.isends) > sends.size()) { + observed.invalid_call = true; + return; + } + auto const index = static_cast(observed.isends - 1); + for (auto prior = std::size_t{0}; prior < index; ++prior) { + if (sends[prior].destination == destination) { + observed.repeated_destination = true; + } + } + sends[index] = { + .request = request, + .buffer = buffer, + .count = count, + .destination = destination, + .checksum = checksum(buffer, count), + .completed = false, + }; +} + +void observe_completion(MPI_Request request) noexcept { + auto const end = + std::min(static_cast(observed.isends), sends.size()); + for (auto index = std::size_t{0}; index < end; ++index) { + auto& send = sends[index]; + if (send.request == request && !send.completed) { + if (checksum(send.buffer, send.count) != send.checksum) { + observed.buffer_changed_before_completion = true; + } + send.completed = true; + return; + } + } + observed.invalid_call = true; +} + +void finalize_observation() noexcept { + auto const end = + std::min(static_cast(observed.isends), sends.size()); + for (auto index = std::size_t{0}; index < end; ++index) { + observed.unfinished += sends[index].completed ? 0 : 1; + } + active_phase = phase::inactive; +} +} // namespace evolutionary_lifetime_probe + +static_assert(noexcept(evolutionary_lifetime_probe::checksum(nullptr, 0))); +static_assert(noexcept(evolutionary_lifetime_probe::reset( + evolutionary_lifetime_probe::phase::inactive, + MPI_COMM_NULL, + 0, + 0))); +static_assert(noexcept( + evolutionary_lifetime_probe::record_send(MPI_REQUEST_NULL, nullptr, 0, + 0))); +static_assert(noexcept( + evolutionary_lifetime_probe::observe_completion(MPI_REQUEST_NULL))); +static_assert(noexcept(evolutionary_lifetime_probe::finalize_observation())); + +extern "C" int MPI_Comm_dup(MPI_Comm communicator, MPI_Comm* duplicate) { + using namespace evolutionary_lifetime_probe; + if (active_phase != phase::communicator) { + return PMPI_Comm_dup(communicator, duplicate); + } + ++observed.duplications; + if (communicator != source_communicator || duplicate == nullptr) { + observed.invalid_call = true; + } + auto const result = PMPI_Comm_dup(communicator, duplicate); + if (result == MPI_SUCCESS && duplicate != nullptr) { + duplicated_communicator = *duplicate; + } + return result; +} + +extern "C" int MPI_Comm_set_errhandler(MPI_Comm communicator, + MPI_Errhandler error_handler) { + using namespace evolutionary_lifetime_probe; + if (active_phase == phase::communicator) { + ++observed.error_handler_sets; + if (communicator != duplicated_communicator || + error_handler != MPI_ERRORS_RETURN) { + observed.invalid_call = true; + } + } + return PMPI_Comm_set_errhandler(communicator, error_handler); +} + +extern "C" int MPI_Comm_free(MPI_Comm* communicator) { + using namespace evolutionary_lifetime_probe; + if (active_phase == phase::communicator) { + ++observed.frees; + if (communicator == nullptr || *communicator != duplicated_communicator) { + observed.invalid_call = true; + } + } + return PMPI_Comm_free(communicator); +} + +extern "C" int MPI_Isend(void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator, + MPI_Request* request) { + using namespace evolutionary_lifetime_probe; + if (active_phase != phase::exchange) { + return PMPI_Isend(buffer, count, datatype, destination, tag, communicator, + request); + } + ++observed.isends; + if (communicator != source_communicator || buffer == nullptr || + count != expected_count || datatype != MPI_INT || destination < 0 || + destination == expected_rank || tag != destination || + request == nullptr) { + observed.invalid_call = true; + } + auto const result = PMPI_Isend(buffer, count, datatype, destination, tag, + communicator, request); + if (result == MPI_SUCCESS && request != nullptr) { + record_send(*request, static_cast(buffer), count, destination); + } + return result; +} + +extern "C" int MPI_Test(MPI_Request* request, + int* completed, + MPI_Status* status) { + using namespace evolutionary_lifetime_probe; + if (active_phase != phase::exchange) { + return PMPI_Test(request, completed, status); + } + ++observed.tests; + if (request == nullptr || completed == nullptr) { + observed.invalid_call = true; + return MPI_ERR_ARG; + } + auto const original = *request; + auto const result = PMPI_Test(request, completed, status); + if (result == MPI_SUCCESS && *completed != 0) { + observe_completion(original); + } + return result; +} + +extern "C" int MPI_Wait(MPI_Request* request, MPI_Status* status) { + using namespace evolutionary_lifetime_probe; + if (active_phase != phase::exchange) { + return PMPI_Wait(request, status); + } + ++observed.waits; + if (request == nullptr) { + observed.invalid_call = true; + return MPI_ERR_ARG; + } + auto const original = *request; + auto const result = PMPI_Wait(request, status); + if (result == MPI_SUCCESS) { + observe_completion(original); + } + return result; +} + +extern "C" int MPI_Cancel(MPI_Request* request) { + using namespace evolutionary_lifetime_probe; + if (active_phase == phase::exchange) { + ++observed.cancellations; + } + return PMPI_Cancel(request); +} + +extern "C" int MPI_Sendrecv(void const* send_buffer, + int send_count, + MPI_Datatype send_datatype, + int destination, + int send_tag, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_datatype, + int source, + int receive_tag, + MPI_Comm communicator, + MPI_Status* status) { + using namespace evolutionary_lifetime_probe; + if (active_phase == phase::exchange) { + ++observed.sendrecvs; + if (communicator != source_communicator || send_buffer == nullptr || + receive_buffer == nullptr || send_count != expected_count || + receive_count != expected_count || send_datatype != MPI_INT || + receive_datatype != MPI_INT || destination < 0 || source < 0 || + send_tag != 0 || receive_tag != 0 || status == nullptr) { + observed.invalid_call = true; + } + } + return PMPI_Sendrecv(send_buffer, send_count, send_datatype, destination, + send_tag, receive_buffer, receive_count, + receive_datatype, source, receive_tag, communicator, + status); +} + +extern "C" int MPI_Recv(void* buffer, + int count, + MPI_Datatype datatype, + int source, + int tag, + MPI_Comm communicator, + MPI_Status* status) { + using namespace evolutionary_lifetime_probe; + if (active_phase == phase::exchange) { + ++observed.receives; + if (communicator != source_communicator || buffer == nullptr || + count != expected_count || datatype != MPI_INT || source < 0 || + tag != expected_rank || status == nullptr) { + observed.invalid_call = true; + } + } + return PMPI_Recv(buffer, count, datatype, source, tag, communicator, status); +} +// KAHIP_PMPI_CALLBACK_REGION_END + +namespace { +void build_cycle_graph(kahip::modified::graph_access& graph, int rank) { + constexpr auto nodes = kahip::modified::NodeID{8}; + graph.start_construction(nodes, 2 * nodes); + graph.set_partition_count(2); + for (auto node = kahip::modified::NodeID{0}; node < nodes; ++node) { + auto const created = graph.new_node(); + graph.setNodeWeight(created, 1); + graph.setPartitionIndex(created, + static_cast( + (node + static_cast(rank)) % 2)); + auto const previous = (node + nodes - 1) % nodes; + auto const next = (node + 1) % nodes; + auto const first = graph.new_edge(node, previous); + graph.setEdgeWeight(first, 1); + auto const second = graph.new_edge(node, next); + graph.setEdgeWeight(second, 1); + } + graph.finish_construction(); +} + +[[nodiscard]] auto make_config() -> kahip::modified::PartitionConfig { + auto config = kahip::modified::PartitionConfig{}; + config.k = 2; + auto defaults = kahip::modified::configuration{}; + defaults.standard(config); + config.mh_pool_size = 64; + config.mh_num_ncs_to_compute = 0; + config.mh_optimize_communication_volume = false; + config.mh_penalty_for_unconnected = false; + config.largest_graph_weight = 8; + config.upper_bound_partition = 8; + return config; +} + +[[nodiscard]] auto make_individual(kahip::modified::graph_access& graph, + int rank) -> kahip::modified::Individuum { + auto result = kahip::modified::Individuum{ + .partition_map = new int[graph.number_of_nodes()], + .objective = rank + 1, + .cut_edges = new std::vector{}, + }; + for (auto node = kahip::modified::NodeID{0}; node < graph.number_of_nodes(); + ++node) { + result.partition_map[node] = graph.getPartitionIndex(node); + } + for (auto node = kahip::modified::NodeID{0}; node < graph.number_of_nodes(); + ++node) { + for (auto edge = graph.get_first_edge(node); + edge < graph.get_first_invalid_edge(node); ++edge) { + if (result.partition_map[node] != + result.partition_map[graph.getEdgeTarget(edge)]) { + result.cut_edges->push_back(edge); + } + } + } + return result; +} + +void reset_graph_partition(kahip::modified::graph_access& graph, int rank) { + for (auto node = kahip::modified::NodeID{0}; node < graph.number_of_nodes(); + ++node) { + graph.setPartitionIndex(node, + static_cast( + (node + static_cast(rank)) % 2)); + } +} + +[[nodiscard]] auto partition_vector(kahip::modified::graph_access& graph) + -> std::vector { + auto result = std::vector( + static_cast(graph.number_of_nodes())); + for (auto node = kahip::modified::NodeID{0}; node < graph.number_of_nodes(); + ++node) { + result[node] = graph.getPartitionIndex(node); + } + return result; +} +} // namespace + +TEST_CASE("evolutionary driver owns an errors-return communicator") { + auto rank = -1; + REQUIRE(PMPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + + auto caller = MPI_COMM_NULL; + REQUIRE(PMPI_Comm_split(MPI_COMM_WORLD, 0, rank, &caller) == MPI_SUCCESS); + REQUIRE(PMPI_Comm_set_errhandler(caller, MPI_ERRORS_RETURN) == MPI_SUCCESS); + + evolutionary_lifetime_probe::reset( + evolutionary_lifetime_probe::phase::communicator, caller, rank, 0); + { + auto driver = kahip::modified::parallel_mh_async{caller}; + static_cast(driver); + } + evolutionary_lifetime_probe::finalize_observation(); + auto const observed = evolutionary_lifetime_probe::observed; + + CHECK(observed.duplications == 1); + CHECK(observed.error_handler_sets == 1); + CHECK(observed.frees == 1); + CHECK_FALSE(observed.invalid_call); + REQUIRE(PMPI_Comm_free(&caller) == MPI_SUCCESS); +} + +TEST_CASE("evolutionary gossip owns payloads through exact P2P completion") { + auto rank = -1; + auto size = 0; + REQUIRE(PMPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(PMPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE((size == 2 || size == 3 || size == 5)); + + auto communicator = MPI_COMM_NULL; + REQUIRE(PMPI_Comm_split(MPI_COMM_WORLD, 0, size - rank, &communicator) == + MPI_SUCCESS); + REQUIRE(PMPI_Comm_rank(communicator, &rank) == MPI_SUCCESS); + REQUIRE(PMPI_Comm_set_errhandler(communicator, MPI_ERRORS_RETURN) == + MPI_SUCCESS); + + auto graph = kahip::modified::graph_access{}; + build_cycle_graph(graph, rank); + auto config = make_config(); + auto island = kahip::modified::population{communicator, config}; + auto initial = make_individual(graph, rank); + island.insert(graph, initial); + kahip::modified::random_functions::setSeed(127 + rank); + + evolutionary_lifetime_probe::reset( + evolutionary_lifetime_probe::phase::exchange, communicator, rank, + static_cast(graph.number_of_nodes())); + { + auto exchange = kahip::modified::exchanger{communicator}; + exchange.diversify_population(config, graph, island, false); + for (auto iteration = 0; iteration < size + 1; ++iteration) { + exchange.push_best(config, graph, island); + REQUIRE(PMPI_Barrier(communicator) == MPI_SUCCESS); + exchange.recv_incoming(config, graph, island); + REQUIRE(PMPI_Barrier(communicator) == MPI_SUCCESS); + } + exchange.finish(static_cast(graph.number_of_nodes())); + } + evolutionary_lifetime_probe::finalize_observation(); + auto const local = evolutionary_lifetime_probe::observed; + + auto local_values = std::array{ + local.isends, + local.tests, + local.waits, + local.cancellations, + local.sendrecvs, + local.receives, + local.unfinished, + local.invalid_call || local.buffer_changed_before_completion ? 1 : 0, + }; + auto global = std::array{}; + REQUIRE(PMPI_Allreduce(local_values.data(), global.data(), + static_cast(local_values.size()), MPI_INT, + MPI_SUM, communicator) == MPI_SUCCESS); + + CHECK(global[0] >= size); + CHECK(global[1] + global[2] >= global[0]); + CHECK(global[3] == 0); + CHECK(global[4] == size); + CHECK(global[5] >= global[0]); + CHECK(global[6] == 0); + CHECK(global[7] == 0); + REQUIRE(PMPI_Comm_free(&communicator) == MPI_SUCCESS); +} + +TEST_CASE("evolutionary gossip contacts only unsent peers before a reset") { + auto rank = -1; + auto size = 0; + REQUIRE(PMPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(PMPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE((size == 2 || size == 3 || size == 5)); + + auto communicator = MPI_COMM_NULL; + REQUIRE(PMPI_Comm_split(MPI_COMM_WORLD, 0, size - rank, &communicator) == + MPI_SUCCESS); + REQUIRE(PMPI_Comm_rank(communicator, &rank) == MPI_SUCCESS); + REQUIRE(PMPI_Comm_set_errhandler(communicator, MPI_ERRORS_RETURN) == + MPI_SUCCESS); + + auto graph = kahip::modified::graph_access{}; + build_cycle_graph(graph, rank); + auto config = make_config(); + auto island = kahip::modified::population{communicator, config}; + auto initial = make_individual(graph, rank); + island.insert(graph, initial); + kahip::modified::random_functions::setSeed(127 + rank); + + evolutionary_lifetime_probe::reset( + evolutionary_lifetime_probe::phase::exchange, communicator, rank, + static_cast(graph.number_of_nodes())); + { + auto exchange = kahip::modified::exchanger{communicator}; + // More calls than the logarithmic push budget make duplicate selection + // observable while the exact issued-count drain keeps teardown finite. + for (auto iteration = 0; iteration < size + 2; ++iteration) { + exchange.push_best(config, graph, island); + } + exchange.finish(static_cast(graph.number_of_nodes())); + } + evolutionary_lifetime_probe::finalize_observation(); + + auto const local_repeat = + evolutionary_lifetime_probe::observed.repeated_destination ? 1 : 0; + auto repeated_ranks = 0; + REQUIRE(PMPI_Allreduce(&local_repeat, &repeated_ranks, 1, MPI_INT, MPI_SUM, + communicator) == MPI_SUCCESS); + CHECK(repeated_ranks == 0); + CHECK_FALSE(evolutionary_lifetime_probe::observed.invalid_call); + CHECK(evolutionary_lifetime_probe::observed.unfinished == 0); + REQUIRE(PMPI_Comm_free(&communicator) == MPI_SUCCESS); +} + +TEST_CASE( + "standard evolutionary configuration selects recursive initial " + "partitioning") { + auto config = kahip::modified::PartitionConfig{}; + config.k = 2; + config.initial_partitioning_type = + kahip::modified::INITIAL_PARTITIONING_BIPARTITION; + + auto defaults = kahip::modified::configuration{}; + defaults.standard(config); + + CHECK(config.initial_partitioning_type == + kahip::modified::INITIAL_PARTITIONING_RECPARTITION); +} + +TEST_CASE("modified evolutionary feasibility sums vertex weights") { + auto graph = kahip::modified::graph_access{}; + build_cycle_graph(graph, 0); + for (auto node = kahip::modified::NodeID{0}; + node < graph.number_of_nodes(); ++node) { + graph.setPartitionIndex( + node, node < 4 ? kahip::modified::PartitionID{0} + : kahip::modified::PartitionID{1}); + graph.setNodeWeight(node, node == 0 ? kahip::modified::NodeWeight{10} + : kahip::modified::NodeWeight{1}); + } + + CHECK(::kahip::parallel_mh::maximum_block_weight< + kahip::modified::NodeWeight>(graph) == + kahip::modified::NodeWeight{13}); +} + +TEST_CASE("modified evolutionary feasibility detects weight overflow") { + auto graph = kahip::modified::graph_access{}; + build_cycle_graph(graph, 0); + for (auto node = kahip::modified::NodeID{0}; + node < graph.number_of_nodes(); ++node) { + graph.setPartitionIndex(node, kahip::modified::PartitionID{0}); + graph.setNodeWeight(node, kahip::modified::NodeWeight{0}); + } + graph.setNodeWeight( + 0, std::numeric_limits::max()); + graph.setNodeWeight(1, kahip::modified::NodeWeight{1}); + + CHECK_FALSE( + ::kahip::parallel_mh::maximum_block_weight< + kahip::modified::NodeWeight>(graph) + .has_value()); +} + +TEST_CASE("private modified kaffpaE honors the authoritative exact bound") { + auto rank = -1; + auto size = 0; + REQUIRE(PMPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(PMPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 2) { + return; + } + + auto n = 4; + auto vertex_weights = std::array{2, 2, 1, 1}; + auto offsets = std::array{0, 2, 4, 6, 8}; + auto neighbors = std::array{1, 3, 0, 2, 1, 3, 2, 0}; + auto blocks = 2; + auto edge_cut = -1; + auto balance = 0.0; + auto partition = rank == 0 ? std::array{0, 0, 1, 1} + : std::array{0, 1, 0, 1}; + + kahip::modified::kaffpaE_with_upper_bound( + &n, vertex_weights.data(), offsets.data(), nullptr, neighbors.data(), + &blocks, true, true, 0, 1, ULTRAFASTSOCIAL, MPI_COMM_WORLD, 33U, + std::uint64_t{3}, &edge_cut, &balance, partition.data()); + + CHECK(partition == std::array{0, 1, 0, 1}); + CHECK(edge_cut == 4); +} + +TEST_CASE("public kaffpaE uses the authoritative exact integer bound") { + auto rank = -1; + auto size = 0; + REQUIRE(PMPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(PMPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 2) { + return; + } + + // total weight 6, k=2, p=33: floor(133 * ceil(6 / 2) / 100) = 3. + // The legacy floating expression rounds this up to 4 and incorrectly admits + // rank zero's overweight starting partition. + auto n = 4; + auto vertex_weights = std::array{2, 2, 1, 1}; + auto offsets = std::array{0, 2, 4, 6, 8}; + auto neighbors = std::array{1, 3, 0, 2, 1, 3, 2, 0}; + auto blocks = 2; + auto imbalance = 0.33; + auto edge_cut = -1; + auto balance = 0.0; + auto partition = rank == 0 ? std::array{0, 0, 1, 1} + : std::array{0, 1, 0, 1}; + + kaffpaE(&n, vertex_weights.data(), offsets.data(), nullptr, + neighbors.data(), &blocks, &imbalance, true, true, 0, 1, + ULTRAFASTSOCIAL, MPI_COMM_WORLD, &edge_cut, &balance, + partition.data()); + + CHECK(partition == std::array{0, 1, 0, 1}); + CHECK(edge_cut == 4); +} + +TEST_CASE("initial partition refinement rejects a missing block target") { + auto config = make_config(); + config.target_weights = {4}; + + CHECK_THROWS_AS(kahip::modified::ip_partition_accept_rule( + config, 2, 4, 4, kahip::modified::PartitionID{0}, + kahip::modified::PartitionID{1}), + std::invalid_argument); +} + +TEST_CASE("reusing an evolutionary driver resets first-run state") { + auto rank = -1; + auto size = 0; + REQUIRE(PMPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(PMPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE((size == 2 || size == 3 || size == 5)); + + auto communicator = MPI_COMM_NULL; + REQUIRE(PMPI_Comm_split(MPI_COMM_WORLD, 0, rank, &communicator) == + MPI_SUCCESS); + REQUIRE(PMPI_Comm_set_errhandler(communicator, MPI_ERRORS_RETURN) == + MPI_SUCCESS); + + auto graph = kahip::modified::graph_access{}; + build_cycle_graph(graph, rank); + auto config = make_config(); + config.seed = 19; + config.time_limit = 0.0; + config.mh_pool_size = 3; + config.mh_enable_quickstart = true; + config.mh_disable_combine = true; + config.mh_diversify = false; + config.local_partitioning_repetitions = 1; + config.ultra_fast_kaffpaE_interfacecall = false; + + auto driver = kahip::modified::parallel_mh_async{communicator}; + driver.perform_partitioning(config, graph); + auto const first_partition = partition_vector(graph); + auto metrics = kahip::modified::quality_metrics{}; + auto const first_cut = metrics.edge_cut(graph); + + reset_graph_partition(graph, rank); + driver.perform_partitioning(config, graph); + auto const second_partition = partition_vector(graph); + auto const second_cut = metrics.edge_cut(graph); + + CHECK(second_partition == first_partition); + CHECK(second_cut == first_cut); + REQUIRE(PMPI_Comm_free(&communicator) == MPI_SUCCESS); +} diff --git a/parallel/parallel_src/tests/evolutionary/evolutionary_population_estimate_test.cpp b/parallel/parallel_src/tests/evolutionary/evolutionary_population_estimate_test.cpp new file mode 100644 index 00000000..165ab9d6 --- /dev/null +++ b/parallel/parallel_src/tests/evolutionary/evolutionary_population_estimate_test.cpp @@ -0,0 +1,56 @@ +#include + +#include +#include + +#include "parallel_mh/evolutionary_collectives.h" +#include "parallel_mh/population_size_broadcast.h" + +TEST_CASE( + "evolutionary population timing estimate is bounded and deterministic") { + using kahip::parallel_mh::estimate_population_size; + + CHECK(estimate_population_size(100.0, 10.0, 2.0, false) == 5); + CHECK(estimate_population_size(100.0, 10.0, 3.0, false) == 4); + CHECK(estimate_population_size(0.0, 10.0, 2.0, false) == 3); + CHECK(estimate_population_size(100.0, 10.0, 0.0, false) == 100); + CHECK(estimate_population_size(100.0, 10.0, 0.0, true) == 50); + CHECK(estimate_population_size(1.0e300, 1.0e-300, 1.0e-300, false) == 100); +} + +TEST_CASE("evolutionary population timing estimate rejects invalid domains") { + using kahip::parallel_mh::estimate_population_size; + auto const nan = std::numeric_limits::quiet_NaN(); + auto const infinity = std::numeric_limits::infinity(); + + CHECK_FALSE(estimate_population_size(-1.0, 10.0, 2.0, false)); + CHECK_FALSE(estimate_population_size(100.0, 0.0, 2.0, false)); + CHECK_FALSE(estimate_population_size(100.0, -1.0, 2.0, false)); + CHECK_FALSE(estimate_population_size(100.0, 10.0, -1.0, false)); + CHECK_FALSE(estimate_population_size(nan, 10.0, 2.0, false)); + CHECK_FALSE(estimate_population_size(100.0, nan, 2.0, false)); + CHECK_FALSE(estimate_population_size(100.0, 10.0, nan, false)); + CHECK_FALSE(estimate_population_size(infinity, 10.0, 2.0, false)); +} + +TEST_CASE("evolutionary quick start handles empty and undersubscribed pools") { + using kahip::parallel_mh::quick_start_plan; + using kahip::parallel_mh::quick_start_population_plan; + + CHECK((quick_start_population_plan(0U, 5) == quick_start_plan{0U, 0U})); + CHECK((quick_start_population_plan(1U, 5) == quick_start_plan{0U, 1U})); + CHECK((quick_start_population_plan(3U, 5) == quick_start_plan{0U, 3U})); + CHECK((quick_start_population_plan(64U, 5) == quick_start_plan{12U, 52U})); + CHECK_FALSE(quick_start_population_plan(64U, 0)); +} + +TEST_CASE("evolutionary objective ordering preserves the weight domain") { + using kahip::parallel_mh::objective_improved; + auto const above_int = + static_cast(std::numeric_limits::max()) + 1; + + CHECK(objective_improved(above_int, above_int + 1)); + CHECK_FALSE(objective_improved(above_int + 1, above_int)); + CHECK(objective_improved(std::int64_t{-1}, std::int64_t{0})); + CHECK_FALSE(objective_improved(std::int64_t{0}, std::int64_t{-1})); +} diff --git a/parallel/parallel_src/tests/evolutionary/root_evolutionary_failure_probe.cpp b/parallel/parallel_src/tests/evolutionary/root_evolutionary_failure_probe.cpp new file mode 100644 index 00000000..3e074c54 --- /dev/null +++ b/parallel/parallel_src/tests/evolutionary/root_evolutionary_failure_probe.cpp @@ -0,0 +1,479 @@ +#include +#include + +#include +#include +#include +#include +#include + +#include "configuration.h" +#include "data_structure/graph_access.h" +#include "parallel_mh/exchange/exchanger.h" +#include "parallel_mh/parallel_mh_async.h" +#include "parallel_mh/population.h" +#include "tools/random_functions.h" + +// KAHIP_PMPI_CALLBACK_REGION_BEGIN +namespace evolutionary_failure_probe { +enum class mode : unsigned char { + communicator_duplication, + sendrecv, + isend, + wrong_tag, + wrong_count, + wait, + unfinished_teardown, + combine_cross_conditional, + invalid_label, + weight_overflow, +}; + +inline bool active = false; +inline mode selected = mode::communicator_duplication; +inline MPI_Comm expected_communicator = MPI_COMM_NULL; +inline int duplications = 0; +inline int sendrecvs = 0; +inline int isends = 0; +inline int tests = 0; +inline int waits = 0; +inline int receives = 0; +inline int cancellations = 0; +inline int finalizations = 0; +inline int communicator_size_queries = 0; +inline int broadcasts = 0; +inline bool callback_error = false; + +[[nodiscard]] auto feasibility_failure(mode value) noexcept -> bool { + return value == mode::invalid_label || value == mode::weight_overflow; +} + +void write_text(std::string_view text) noexcept { + static_cast(::write(STDERR_FILENO, text.data(), text.size())); +} + +[[nodiscard]] auto expected_abort_state() noexcept -> bool { + if (callback_error || finalizations != 0 || cancellations != 0) { + return false; + } + switch (selected) { + case mode::communicator_duplication: + return duplications == 1; + case mode::sendrecv: + return sendrecvs == 1; + case mode::isend: + return isends == 1; + case mode::wrong_tag: + case mode::wrong_count: + return receives == 0; + case mode::wait: + return isends == 1 && tests >= 1 && waits == 1; + case mode::unfinished_teardown: + return duplications == 0 && sendrecvs == 0 && isends == 0 && + tests == 0 && waits == 0 && receives == 0; + case mode::combine_cross_conditional: + return communicator_size_queries == 1 && broadcasts == 0; + case mode::invalid_label: + case mode::weight_overflow: + return duplications == 1 && broadcasts == 0; + } + return false; +} + +[[noreturn]] void observed_abort(MPI_Comm communicator, + int error_code) noexcept { + auto relation = int{MPI_UNEQUAL}; + if (expected_communicator == MPI_COMM_NULL || error_code != EXIT_FAILURE || + PMPI_Comm_compare(communicator, expected_communicator, &relation) != + MPI_SUCCESS || + relation != MPI_IDENT || !expected_abort_state()) { + write_text( + "observed evolutionary lifetime MPI_Abort with unexpected state\n"); + std::_Exit(91); + } + write_text( + "observed evolutionary lifetime MPI_Abort on affected communicator\n"); + if (PMPI_Barrier(MPI_COMM_WORLD) != MPI_SUCCESS) { + std::_Exit(90); + } + std::_Exit(86); +} +} // namespace evolutionary_failure_probe + +static_assert(noexcept(evolutionary_failure_probe::write_text({}))); +static_assert(noexcept(evolutionary_failure_probe::expected_abort_state())); +static_assert(noexcept(evolutionary_failure_probe::observed_abort(MPI_COMM_NULL, + 0))); + +extern "C" int MPI_Comm_dup(MPI_Comm communicator, MPI_Comm* duplicate) { + using namespace evolutionary_failure_probe; + if (!active) { + return PMPI_Comm_dup(communicator, duplicate); + } + ++duplications; + if (feasibility_failure(selected)) { + if (communicator != expected_communicator || duplicate == nullptr) { + callback_error = true; + return MPI_ERR_OTHER; + } + auto const result = PMPI_Comm_dup(communicator, duplicate); + if (result == MPI_SUCCESS) { + expected_communicator = *duplicate; + } + return result; + } + if (selected != mode::communicator_duplication || + communicator != expected_communicator || duplicate == nullptr) { + callback_error = true; + } + return MPI_ERR_OTHER; +} + +extern "C" int MPI_Bcast(void* buffer, + int count, + MPI_Datatype datatype, + int root, + MPI_Comm communicator) { + using namespace evolutionary_failure_probe; + if (active && feasibility_failure(selected)) { + ++broadcasts; + callback_error = true; + } + if (active && selected == mode::combine_cross_conditional) { + ++broadcasts; + callback_error = true; + return MPI_ERR_OTHER; + } + return PMPI_Bcast(buffer, count, datatype, root, communicator); +} + +extern "C" int MPI_Comm_size(MPI_Comm communicator, int* size) { + using namespace evolutionary_failure_probe; + if (active && selected == mode::combine_cross_conditional) { + ++communicator_size_queries; + if (communicator != expected_communicator || size == nullptr) { + callback_error = true; + return MPI_ERR_OTHER; + } + } + return PMPI_Comm_size(communicator, size); +} + +extern "C" int MPI_Sendrecv(void const* send_buffer, + int send_count, + MPI_Datatype send_datatype, + int destination, + int send_tag, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_datatype, + int source, + int receive_tag, + MPI_Comm communicator, + MPI_Status* status) { + using namespace evolutionary_failure_probe; + if (!active) { + return PMPI_Sendrecv(send_buffer, send_count, send_datatype, destination, + send_tag, receive_buffer, receive_count, + receive_datatype, source, receive_tag, communicator, + status); + } + ++sendrecvs; + if (selected != mode::sendrecv || communicator != expected_communicator || + send_buffer == nullptr || receive_buffer == nullptr || send_count != 8 || + receive_count != 8 || send_datatype != MPI_INT || + receive_datatype != MPI_INT || send_tag != 0 || receive_tag != 0 || + destination < 0 || source < 0 || status == nullptr) { + callback_error = true; + } + return MPI_ERR_OTHER; +} + +extern "C" int MPI_Isend(void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator, + MPI_Request* request) { + using namespace evolutionary_failure_probe; + if (!active) { + return PMPI_Isend(buffer, count, datatype, destination, tag, communicator, + request); + } + ++isends; + if ((selected != mode::isend && selected != mode::wait) || + communicator != expected_communicator || buffer == nullptr || + count != 8 || datatype != MPI_INT || destination < 0 || + tag != destination || request == nullptr) { + callback_error = true; + } + if (selected == mode::isend) { + return MPI_ERR_OTHER; + } + return PMPI_Isend(buffer, count, datatype, destination, tag, communicator, + request); +} + +extern "C" int MPI_Test(MPI_Request* request, + int* completed, + MPI_Status* status) { + using namespace evolutionary_failure_probe; + if (!active) { + return PMPI_Test(request, completed, status); + } + ++tests; + if (selected != mode::wait || request == nullptr || completed == nullptr) { + callback_error = true; + return MPI_ERR_OTHER; + } + *completed = 0; + return MPI_SUCCESS; +} + +extern "C" int MPI_Wait(MPI_Request* request, MPI_Status* status) { + using namespace evolutionary_failure_probe; + if (!active) { + return PMPI_Wait(request, status); + } + ++waits; + if (selected != mode::wait || request == nullptr) { + callback_error = true; + } + return MPI_ERR_OTHER; +} + +extern "C" int MPI_Recv(void* buffer, + int count, + MPI_Datatype datatype, + int source, + int tag, + MPI_Comm communicator, + MPI_Status* status) { + if (evolutionary_failure_probe::active) { + ++evolutionary_failure_probe::receives; + } + return PMPI_Recv(buffer, count, datatype, source, tag, communicator, status); +} + +extern "C" int MPI_Cancel(MPI_Request* request) { + if (evolutionary_failure_probe::active) { + ++evolutionary_failure_probe::cancellations; + } + return PMPI_Cancel(request); +} + +extern "C" int MPI_Finalize() { + if (evolutionary_failure_probe::active) { + ++evolutionary_failure_probe::finalizations; + return MPI_SUCCESS; + } + return PMPI_Finalize(); +} + +extern "C" int MPI_Abort(MPI_Comm communicator, int error_code) { + evolutionary_failure_probe::observed_abort(communicator, error_code); +} +// KAHIP_PMPI_CALLBACK_REGION_END + +namespace { +[[nodiscard]] auto parse_mode(std::string_view value) + -> evolutionary_failure_probe::mode { + using mode = evolutionary_failure_probe::mode; + if (value == "communicator-duplication") { + return mode::communicator_duplication; + } + if (value == "sendrecv") + return mode::sendrecv; + if (value == "isend") + return mode::isend; + if (value == "wrong-tag") + return mode::wrong_tag; + if (value == "wrong-count") + return mode::wrong_count; + if (value == "wait") + return mode::wait; + if (value == "unfinished-teardown") + return mode::unfinished_teardown; + if (value == "combine-cross-conditional") + return mode::combine_cross_conditional; + if (value == "invalid-label") + return mode::invalid_label; + if (value == "weight-overflow") + return mode::weight_overflow; + evolutionary_failure_probe::write_text( + "unknown evolutionary lifetime failure mode\n"); + std::_Exit(2); +} + +void build_cycle_graph(graph_access& graph, int rank) { + constexpr auto nodes = NodeID{8}; + graph.start_construction(nodes, 2 * nodes); + graph.set_partition_count(2); + for (auto node = NodeID{0}; node < nodes; ++node) { + auto const created = graph.new_node(); + graph.setNodeWeight(created, 1); + graph.setPartitionIndex(created, + static_cast( + (node + static_cast(rank)) % 2)); + for (auto const target : + std::array{(node + nodes - 1) % nodes, (node + 1) % nodes}) { + auto const edge = graph.new_edge(node, target); + graph.setEdgeWeight(edge, 1); + } + } + graph.finish_construction(); +} + +[[nodiscard]] auto make_config() -> PartitionConfig { + auto config = PartitionConfig{}; + config.k = 2; + auto defaults = configuration{}; + defaults.standard(config); + config.mh_pool_size = 64; + config.mh_num_ncs_to_compute = 0; + config.mh_optimize_communication_volume = false; + config.mh_penalty_for_unconnected = false; + config.largest_graph_weight = 8; + config.upper_bound_partition = 8; + return config; +} + +[[nodiscard]] auto make_individual(graph_access& graph, + int rank) -> Individuum { + auto result = Individuum{ + .partition_map = new int[graph.number_of_nodes()], + .objective = rank + 1, + .cut_edges = new std::vector{}, + }; + for (auto node = NodeID{0}; node < graph.number_of_nodes(); + ++node) { + result.partition_map[node] = graph.getPartitionIndex(node); + } + for (auto node = NodeID{0}; node < graph.number_of_nodes(); + ++node) { + for (auto edge = graph.get_first_edge(node); + edge < graph.get_first_invalid_edge(node); ++edge) { + if (result.partition_map[node] != + result.partition_map[graph.getEdgeTarget(edge)]) { + result.cut_edges->push_back(edge); + } + } + } + return result; +} +} // namespace + +int main(int argc, char** argv) { + if (argc != 2 || MPI_Init(&argc, &argv) != MPI_SUCCESS) { + return 2; + } + auto world_rank = -1; + auto world_size = 0; + if (MPI_Comm_rank(MPI_COMM_WORLD, &world_rank) != MPI_SUCCESS || + MPI_Comm_size(MPI_COMM_WORLD, &world_size) != MPI_SUCCESS || + world_size != 2) { + return 3; + } + auto communicator = MPI_COMM_NULL; + if (MPI_Comm_split(MPI_COMM_WORLD, 0, 1 - world_rank, &communicator) != + MPI_SUCCESS || + communicator == MPI_COMM_NULL || + MPI_Comm_set_errhandler(communicator, MPI_ERRORS_RETURN) != MPI_SUCCESS) { + return 4; + } + auto rank = -1; + if (MPI_Comm_rank(communicator, &rank) != MPI_SUCCESS) { + return 5; + } + + auto const selected = parse_mode(argv[1]); + evolutionary_failure_probe::selected = selected; + evolutionary_failure_probe::expected_communicator = communicator; + if (selected == evolutionary_failure_probe::mode::communicator_duplication) { + evolutionary_failure_probe::active = true; + auto driver = parallel_mh_async{communicator}; + static_cast(driver); + } + + auto graph = graph_access{}; + build_cycle_graph(graph, rank); + auto config = make_config(); + auto island = population{communicator, config}; + auto initial = make_individual(graph, rank); + island.insert(graph, initial); + random_functions::setSeed(71 + rank); + using mode = evolutionary_failure_probe::mode; + + if (selected == mode::invalid_label || selected == mode::weight_overflow) { + if (selected == mode::invalid_label) { + graph.setPartitionIndex(0, config.k); + } else { + forall_nodes(graph, node) { + graph.setPartitionIndex(node, PartitionID{0}); + graph.setNodeWeight(node, NodeWeight{0}); + } + endfor + graph.setNodeWeight(0, std::numeric_limits::max()); + graph.setNodeWeight(1, NodeWeight{1}); + } + evolutionary_failure_probe::active = true; + auto driver = parallel_mh_async{communicator}; + static_cast(driver.collect_best_partitioning( + graph, config, static_cast(rank + 1))); + } + + if (selected == mode::unfinished_teardown) { + evolutionary_failure_probe::active = true; + { + auto unfinished_exchange = exchanger{communicator}; + static_cast(unfinished_exchange); + } + } + + if (selected == mode::combine_cross_conditional) { + // Model asynchronous entry: only one process calls combine_cross. The peer + // waits in the PMPI harness solely so the intercepted abort can emit one + // deterministic marker without replacing the real MPI_Abort semantics. + if (rank != 0) { + static_cast(PMPI_Barrier(MPI_COMM_WORLD)); + std::_Exit(85); + } + config.mh_cross_combine_original_k = true; + auto output = Individuum{}; + evolutionary_failure_probe::active = true; + island.combine_cross(config, graph, initial, output); + } + + auto exchange = exchanger{communicator}; + if (selected == mode::wrong_tag || selected == mode::wrong_count) { + auto payload = std::vector(selected == mode::wrong_count + ? graph.number_of_nodes() + 1 + : graph.number_of_nodes(), + rank); + auto request = MPI_REQUEST_NULL; + auto const destination = 1 - rank; + auto const tag = selected == mode::wrong_tag ? 947 : destination; + if (PMPI_Isend(payload.data(), static_cast(payload.size()), MPI_INT, + destination, tag, communicator, &request) != MPI_SUCCESS || + PMPI_Barrier(communicator) != MPI_SUCCESS) { + return 6; + } + evolutionary_failure_probe::active = true; + exchange.recv_incoming(config, graph, island); + } else if (selected == mode::sendrecv) { + evolutionary_failure_probe::active = true; + exchange.diversify_population(config, graph, island, false); + } else if (selected == mode::isend) { + evolutionary_failure_probe::active = true; + exchange.push_best(config, graph, island); + } else if (selected == mode::wait) { + evolutionary_failure_probe::active = true; + exchange.push_best(config, graph, island); + exchange.finish(static_cast(graph.number_of_nodes())); + } + + evolutionary_failure_probe::write_text( + "evolutionary lifetime operation returned without fail-fast\n"); + std::_Exit(92); +} diff --git a/parallel/parallel_src/tests/evolutionary/root_evolutionary_mpi_test.cpp b/parallel/parallel_src/tests/evolutionary/root_evolutionary_mpi_test.cpp new file mode 100644 index 00000000..6b9c8bdb --- /dev/null +++ b/parallel/parallel_src/tests/evolutionary/root_evolutionary_mpi_test.cpp @@ -0,0 +1,667 @@ +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "configuration.h" +#include "data_structure/graph_access.h" +#include "parallel_mh/exchange/exchanger.h" +#include "parallel_mh/parallel_mh_async.h" +#include "parallel_mh/evolutionary_feasibility.h" +#include "parallel_mh/population.h" +#include "partition/initial_partitioning/initial_partitioning.h" +#include "tools/quality_metrics.h" +#include "tools/random_functions.h" + +// KAHIP_PMPI_CALLBACK_REGION_BEGIN +namespace evolutionary_lifetime_probe { +enum class phase : unsigned char { inactive, communicator, exchange }; + +struct send_record final { + MPI_Request request = MPI_REQUEST_NULL; + int const* buffer = nullptr; + int count = 0; + int destination = -1; + std::uint64_t checksum = 0; + bool completed = false; +}; + +struct counters final { + int duplications = 0; + int error_handler_sets = 0; + int frees = 0; + int isends = 0; + int tests = 0; + int waits = 0; + int cancellations = 0; + int sendrecvs = 0; + int receives = 0; + int unfinished = 0; + bool invalid_call = false; + bool buffer_changed_before_completion = false; + bool repeated_destination = false; +}; + +inline constexpr auto maximum_sends = std::size_t{64}; +inline phase active_phase = phase::inactive; +inline MPI_Comm source_communicator = MPI_COMM_NULL; +inline MPI_Comm duplicated_communicator = MPI_COMM_NULL; +inline int expected_count = 0; +inline int expected_rank = -1; +inline counters observed{}; +inline std::array sends{}; + +[[nodiscard]] auto checksum(int const* values, int count) noexcept + -> std::uint64_t { + auto result = std::uint64_t{1469598103934665603ULL}; + if (values == nullptr || count < 0) { + return 0; + } + for (auto index = 0; index < count; ++index) { + result ^= + static_cast(static_cast(values[index])); + result *= std::uint64_t{1099511628211ULL}; + } + return result; +} + +void reset(phase next, + MPI_Comm communicator, + int rank, + int partition_count) noexcept { + active_phase = next; + source_communicator = communicator; + duplicated_communicator = MPI_COMM_NULL; + expected_count = partition_count; + expected_rank = rank; + observed = {}; + sends = {}; +} + +void record_send(MPI_Request request, + int const* buffer, + int count, + int destination) noexcept { + if (observed.isends <= 0 || + static_cast(observed.isends) > sends.size()) { + observed.invalid_call = true; + return; + } + auto const index = static_cast(observed.isends - 1); + for (auto prior = std::size_t{0}; prior < index; ++prior) { + if (sends[prior].destination == destination) { + observed.repeated_destination = true; + } + } + sends[index] = { + .request = request, + .buffer = buffer, + .count = count, + .destination = destination, + .checksum = checksum(buffer, count), + .completed = false, + }; +} + +void observe_completion(MPI_Request request) noexcept { + auto const end = + std::min(static_cast(observed.isends), sends.size()); + for (auto index = std::size_t{0}; index < end; ++index) { + auto& send = sends[index]; + if (send.request == request && !send.completed) { + if (checksum(send.buffer, send.count) != send.checksum) { + observed.buffer_changed_before_completion = true; + } + send.completed = true; + return; + } + } + observed.invalid_call = true; +} + +void finalize_observation() noexcept { + auto const end = + std::min(static_cast(observed.isends), sends.size()); + for (auto index = std::size_t{0}; index < end; ++index) { + observed.unfinished += sends[index].completed ? 0 : 1; + } + active_phase = phase::inactive; +} +} // namespace evolutionary_lifetime_probe + +static_assert(noexcept(evolutionary_lifetime_probe::checksum(nullptr, 0))); +static_assert(noexcept(evolutionary_lifetime_probe::reset( + evolutionary_lifetime_probe::phase::inactive, + MPI_COMM_NULL, + 0, + 0))); +static_assert(noexcept( + evolutionary_lifetime_probe::record_send(MPI_REQUEST_NULL, nullptr, 0, + 0))); +static_assert(noexcept( + evolutionary_lifetime_probe::observe_completion(MPI_REQUEST_NULL))); +static_assert(noexcept(evolutionary_lifetime_probe::finalize_observation())); + +extern "C" int MPI_Comm_dup(MPI_Comm communicator, MPI_Comm* duplicate) { + using namespace evolutionary_lifetime_probe; + if (active_phase != phase::communicator) { + return PMPI_Comm_dup(communicator, duplicate); + } + ++observed.duplications; + if (communicator != source_communicator || duplicate == nullptr) { + observed.invalid_call = true; + } + auto const result = PMPI_Comm_dup(communicator, duplicate); + if (result == MPI_SUCCESS && duplicate != nullptr) { + duplicated_communicator = *duplicate; + } + return result; +} + +extern "C" int MPI_Comm_set_errhandler(MPI_Comm communicator, + MPI_Errhandler error_handler) { + using namespace evolutionary_lifetime_probe; + if (active_phase == phase::communicator) { + ++observed.error_handler_sets; + if (communicator != duplicated_communicator || + error_handler != MPI_ERRORS_RETURN) { + observed.invalid_call = true; + } + } + return PMPI_Comm_set_errhandler(communicator, error_handler); +} + +extern "C" int MPI_Comm_free(MPI_Comm* communicator) { + using namespace evolutionary_lifetime_probe; + if (active_phase == phase::communicator) { + ++observed.frees; + if (communicator == nullptr || *communicator != duplicated_communicator) { + observed.invalid_call = true; + } + } + return PMPI_Comm_free(communicator); +} + +extern "C" int MPI_Isend(void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator, + MPI_Request* request) { + using namespace evolutionary_lifetime_probe; + if (active_phase != phase::exchange) { + return PMPI_Isend(buffer, count, datatype, destination, tag, communicator, + request); + } + ++observed.isends; + if (communicator != source_communicator || buffer == nullptr || + count != expected_count || datatype != MPI_INT || destination < 0 || + destination == expected_rank || tag != destination || + request == nullptr) { + observed.invalid_call = true; + } + auto const result = PMPI_Isend(buffer, count, datatype, destination, tag, + communicator, request); + if (result == MPI_SUCCESS && request != nullptr) { + record_send(*request, static_cast(buffer), count, destination); + } + return result; +} + +extern "C" int MPI_Test(MPI_Request* request, + int* completed, + MPI_Status* status) { + using namespace evolutionary_lifetime_probe; + if (active_phase != phase::exchange) { + return PMPI_Test(request, completed, status); + } + ++observed.tests; + if (request == nullptr || completed == nullptr) { + observed.invalid_call = true; + return MPI_ERR_ARG; + } + auto const original = *request; + auto const result = PMPI_Test(request, completed, status); + if (result == MPI_SUCCESS && *completed != 0) { + observe_completion(original); + } + return result; +} + +extern "C" int MPI_Wait(MPI_Request* request, MPI_Status* status) { + using namespace evolutionary_lifetime_probe; + if (active_phase != phase::exchange) { + return PMPI_Wait(request, status); + } + ++observed.waits; + if (request == nullptr) { + observed.invalid_call = true; + return MPI_ERR_ARG; + } + auto const original = *request; + auto const result = PMPI_Wait(request, status); + if (result == MPI_SUCCESS) { + observe_completion(original); + } + return result; +} + +extern "C" int MPI_Cancel(MPI_Request* request) { + using namespace evolutionary_lifetime_probe; + if (active_phase == phase::exchange) { + ++observed.cancellations; + } + return PMPI_Cancel(request); +} + +extern "C" int MPI_Sendrecv(void const* send_buffer, + int send_count, + MPI_Datatype send_datatype, + int destination, + int send_tag, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_datatype, + int source, + int receive_tag, + MPI_Comm communicator, + MPI_Status* status) { + using namespace evolutionary_lifetime_probe; + if (active_phase == phase::exchange) { + ++observed.sendrecvs; + if (communicator != source_communicator || send_buffer == nullptr || + receive_buffer == nullptr || send_count != expected_count || + receive_count != expected_count || send_datatype != MPI_INT || + receive_datatype != MPI_INT || destination < 0 || source < 0 || + send_tag != 0 || receive_tag != 0 || status == nullptr) { + observed.invalid_call = true; + } + } + return PMPI_Sendrecv(send_buffer, send_count, send_datatype, destination, + send_tag, receive_buffer, receive_count, + receive_datatype, source, receive_tag, communicator, + status); +} + +extern "C" int MPI_Recv(void* buffer, + int count, + MPI_Datatype datatype, + int source, + int tag, + MPI_Comm communicator, + MPI_Status* status) { + using namespace evolutionary_lifetime_probe; + if (active_phase == phase::exchange) { + ++observed.receives; + if (communicator != source_communicator || buffer == nullptr || + count != expected_count || datatype != MPI_INT || source < 0 || + tag != expected_rank || status == nullptr) { + observed.invalid_call = true; + } + } + return PMPI_Recv(buffer, count, datatype, source, tag, communicator, status); +} +// KAHIP_PMPI_CALLBACK_REGION_END + +namespace { +void build_cycle_graph(graph_access& graph, int rank) { + constexpr auto nodes = NodeID{8}; + graph.start_construction(nodes, 2 * nodes); + graph.set_partition_count(2); + for (auto node = NodeID{0}; node < nodes; ++node) { + auto const created = graph.new_node(); + graph.setNodeWeight(created, 1); + graph.setPartitionIndex(created, + static_cast( + (node + static_cast(rank)) % 2)); + auto const previous = (node + nodes - 1) % nodes; + auto const next = (node + 1) % nodes; + auto const first = graph.new_edge(node, previous); + graph.setEdgeWeight(first, 1); + auto const second = graph.new_edge(node, next); + graph.setEdgeWeight(second, 1); + } + graph.finish_construction(); +} + +[[nodiscard]] auto make_config() -> PartitionConfig { + auto config = PartitionConfig{}; + config.k = 2; + auto defaults = configuration{}; + defaults.standard(config); + config.mh_pool_size = 64; + config.mh_num_ncs_to_compute = 0; + config.mh_optimize_communication_volume = false; + config.mh_penalty_for_unconnected = false; + config.largest_graph_weight = 8; + config.upper_bound_partition = 8; + return config; +} + +[[nodiscard]] auto make_individual(graph_access& graph, + int rank) -> Individuum { + auto result = Individuum{ + .partition_map = new int[graph.number_of_nodes()], + .objective = rank + 1, + .cut_edges = new std::vector{}, + }; + for (auto node = NodeID{0}; node < graph.number_of_nodes(); + ++node) { + result.partition_map[node] = graph.getPartitionIndex(node); + } + for (auto node = NodeID{0}; node < graph.number_of_nodes(); + ++node) { + for (auto edge = graph.get_first_edge(node); + edge < graph.get_first_invalid_edge(node); ++edge) { + if (result.partition_map[node] != + result.partition_map[graph.getEdgeTarget(edge)]) { + result.cut_edges->push_back(edge); + } + } + } + return result; +} + +void reset_graph_partition(graph_access& graph, int rank) { + for (auto node = NodeID{0}; node < graph.number_of_nodes(); + ++node) { + graph.setPartitionIndex(node, + static_cast( + (node + static_cast(rank)) % 2)); + } +} + +[[nodiscard]] auto partition_vector(graph_access& graph) + -> std::vector { + auto result = std::vector( + static_cast(graph.number_of_nodes())); + for (auto node = NodeID{0}; node < graph.number_of_nodes(); + ++node) { + result[node] = graph.getPartitionIndex(node); + } + return result; +} + +[[nodiscard]] auto expected_unsent_destinations(int seed, + int rank, + int size) + -> std::vector { + random_functions::setSeed(seed); + auto sent = std::vector(static_cast(size), false); + sent[static_cast(rank)] = true; + auto destinations = std::vector{}; + destinations.reserve(static_cast(size - 1)); + while (destinations.size() < static_cast(size - 1)) { + auto target = rank; + while (sent[static_cast(target)]) { + target = random_functions::nextInt(0, size - 1); + } + sent[static_cast(target)] = true; + destinations.push_back(target); + } + return destinations; +} +} // namespace + +TEST_CASE("evolutionary driver owns an errors-return communicator") { + auto rank = -1; + REQUIRE(PMPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + + auto caller = MPI_COMM_NULL; + REQUIRE(PMPI_Comm_split(MPI_COMM_WORLD, 0, rank, &caller) == MPI_SUCCESS); + REQUIRE(PMPI_Comm_set_errhandler(caller, MPI_ERRORS_RETURN) == MPI_SUCCESS); + + evolutionary_lifetime_probe::reset( + evolutionary_lifetime_probe::phase::communicator, caller, rank, 0); + { + auto driver = parallel_mh_async{caller}; + static_cast(driver); + } + evolutionary_lifetime_probe::finalize_observation(); + auto const observed = evolutionary_lifetime_probe::observed; + + CHECK(observed.duplications == 1); + CHECK(observed.error_handler_sets == 1); + CHECK(observed.frees == 1); + CHECK_FALSE(observed.invalid_call); + REQUIRE(PMPI_Comm_free(&caller) == MPI_SUCCESS); +} + +TEST_CASE("evolutionary gossip owns payloads through exact P2P completion") { + auto rank = -1; + auto size = 0; + REQUIRE(PMPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(PMPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE((size == 2 || size == 3 || size == 5)); + + auto communicator = MPI_COMM_NULL; + REQUIRE(PMPI_Comm_split(MPI_COMM_WORLD, 0, size - rank, &communicator) == + MPI_SUCCESS); + REQUIRE(PMPI_Comm_rank(communicator, &rank) == MPI_SUCCESS); + REQUIRE(PMPI_Comm_set_errhandler(communicator, MPI_ERRORS_RETURN) == + MPI_SUCCESS); + + auto graph = graph_access{}; + build_cycle_graph(graph, rank); + auto config = make_config(); + auto island = population{communicator, config}; + auto initial = make_individual(graph, rank); + island.insert(graph, initial); + random_functions::setSeed(127 + rank); + + evolutionary_lifetime_probe::reset( + evolutionary_lifetime_probe::phase::exchange, communicator, rank, + static_cast(graph.number_of_nodes())); + { + auto exchange = exchanger{communicator}; + exchange.diversify_population(config, graph, island, false); + for (auto iteration = 0; iteration < size + 1; ++iteration) { + exchange.push_best(config, graph, island); + REQUIRE(PMPI_Barrier(communicator) == MPI_SUCCESS); + exchange.recv_incoming(config, graph, island); + REQUIRE(PMPI_Barrier(communicator) == MPI_SUCCESS); + } + exchange.finish(static_cast(graph.number_of_nodes())); + } + evolutionary_lifetime_probe::finalize_observation(); + auto const local = evolutionary_lifetime_probe::observed; + + auto local_values = std::array{ + local.isends, + local.tests, + local.waits, + local.cancellations, + local.sendrecvs, + local.receives, + local.unfinished, + local.invalid_call || local.buffer_changed_before_completion ? 1 : 0, + }; + auto global = std::array{}; + REQUIRE(PMPI_Allreduce(local_values.data(), global.data(), + static_cast(local_values.size()), MPI_INT, + MPI_SUM, communicator) == MPI_SUCCESS); + + CHECK(global[0] >= size); + CHECK(global[1] + global[2] >= global[0]); + CHECK(global[3] == 0); + CHECK(global[4] == size); + CHECK(global[5] >= global[0]); + CHECK(global[6] == 0); + CHECK(global[7] == 0); + REQUIRE(PMPI_Comm_free(&communicator) == MPI_SUCCESS); +} + +TEST_CASE("evolutionary gossip contacts only unsent peers before a reset") { + auto rank = -1; + auto size = 0; + REQUIRE(PMPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(PMPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE((size == 2 || size == 3 || size == 5)); + + auto communicator = MPI_COMM_NULL; + REQUIRE(PMPI_Comm_split(MPI_COMM_WORLD, 0, size - rank, &communicator) == + MPI_SUCCESS); + REQUIRE(PMPI_Comm_rank(communicator, &rank) == MPI_SUCCESS); + REQUIRE(PMPI_Comm_set_errhandler(communicator, MPI_ERRORS_RETURN) == + MPI_SUCCESS); + + auto graph = graph_access{}; + build_cycle_graph(graph, rank); + auto config = make_config(); + auto island = population{communicator, config}; + auto initial = make_individual(graph, rank); + island.insert(graph, initial); + auto const random_seed = 127 + rank; + auto const expected_destinations = + expected_unsent_destinations(random_seed, rank, size); + random_functions::setSeed(random_seed); + + evolutionary_lifetime_probe::reset( + evolutionary_lifetime_probe::phase::exchange, communicator, rank, + static_cast(graph.number_of_nodes())); + { + auto exchange = exchanger{communicator}; + // More calls than the logarithmic push budget make duplicate selection + // observable while the exact issued-count drain keeps teardown finite. + for (auto iteration = 0; iteration < size + 2; ++iteration) { + exchange.push_best(config, graph, island); + } + exchange.finish(static_cast(graph.number_of_nodes())); + } + evolutionary_lifetime_probe::finalize_observation(); + + auto const local_repeat = + evolutionary_lifetime_probe::observed.repeated_destination ? 1 : 0; + auto repeated_ranks = 0; + REQUIRE(PMPI_Allreduce(&local_repeat, &repeated_ranks, 1, MPI_INT, MPI_SUM, + communicator) == MPI_SUCCESS); + CHECK(repeated_ranks == 0); + REQUIRE(evolutionary_lifetime_probe::observed.isends == + static_cast(expected_destinations.size())); + for (auto index = std::size_t{0}; index < expected_destinations.size(); + ++index) { + CHECK(evolutionary_lifetime_probe::sends[index].destination == + expected_destinations[index]); + } + CHECK_FALSE(evolutionary_lifetime_probe::observed.invalid_call); + CHECK(evolutionary_lifetime_probe::observed.unfinished == 0); + REQUIRE(PMPI_Comm_free(&communicator) == MPI_SUCCESS); +} + +TEST_CASE("empty evolutionary exchange finishes without sentinel traffic") { + auto rank = -1; + auto size = 0; + REQUIRE(PMPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(PMPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE((size == 2 || size == 3 || size == 5)); + + auto communicator = MPI_COMM_NULL; + REQUIRE(PMPI_Comm_split(MPI_COMM_WORLD, 0, size - rank, &communicator) == + MPI_SUCCESS); + REQUIRE(PMPI_Comm_rank(communicator, &rank) == MPI_SUCCESS); + REQUIRE(PMPI_Comm_set_errhandler(communicator, MPI_ERRORS_RETURN) == + MPI_SUCCESS); + + evolutionary_lifetime_probe::reset( + evolutionary_lifetime_probe::phase::exchange, communicator, rank, 0); + { + auto exchange = exchanger{communicator}; + exchange.finish(0); + } + evolutionary_lifetime_probe::finalize_observation(); + + CHECK(evolutionary_lifetime_probe::observed.isends == 0); + CHECK(evolutionary_lifetime_probe::observed.receives == 0); + CHECK(evolutionary_lifetime_probe::observed.unfinished == 0); + CHECK_FALSE(evolutionary_lifetime_probe::observed.invalid_call); + REQUIRE(PMPI_Comm_free(&communicator) == MPI_SUCCESS); +} + +TEST_CASE( + "standard evolutionary configuration selects recursive initial " + "partitioning") { + auto config = PartitionConfig{}; + config.k = 2; + config.initial_partitioning_type = + INITIAL_PARTITIONING_BIPARTITION; + + auto defaults = configuration{}; + defaults.standard(config); + + CHECK(config.initial_partitioning_type == + INITIAL_PARTITIONING_RECPARTITION); +} + +TEST_CASE("root evolutionary feasibility sums vertex weights") { + auto graph = graph_access{}; + build_cycle_graph(graph, 0); + for (auto node = NodeID{0}; node < graph.number_of_nodes(); ++node) { + graph.setPartitionIndex(node, node < 4 ? PartitionID{0} : PartitionID{1}); + graph.setNodeWeight(node, node == 0 ? NodeWeight{10} : NodeWeight{1}); + } + + CHECK(::kahip::parallel_mh::maximum_block_weight(graph) == + NodeWeight{13}); +} + +TEST_CASE("root evolutionary feasibility detects weight overflow") { + auto graph = graph_access{}; + build_cycle_graph(graph, 0); + for (auto node = NodeID{0}; node < graph.number_of_nodes(); ++node) { + graph.setPartitionIndex(node, PartitionID{0}); + graph.setNodeWeight(node, NodeWeight{0}); + } + graph.setNodeWeight(0, std::numeric_limits::max()); + graph.setNodeWeight(1, NodeWeight{1}); + + CHECK_FALSE( + ::kahip::parallel_mh::maximum_block_weight(graph) + .has_value()); +} + +TEST_CASE("reusing an evolutionary driver resets first-run state") { + auto rank = -1; + auto size = 0; + REQUIRE(PMPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(PMPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE((size == 2 || size == 3 || size == 5)); + + auto communicator = MPI_COMM_NULL; + REQUIRE(PMPI_Comm_split(MPI_COMM_WORLD, 0, rank, &communicator) == + MPI_SUCCESS); + REQUIRE(PMPI_Comm_set_errhandler(communicator, MPI_ERRORS_RETURN) == + MPI_SUCCESS); + + auto graph = graph_access{}; + build_cycle_graph(graph, rank); + auto config = make_config(); + config.seed = 19; + config.time_limit = 0.0; + config.mh_pool_size = 3; + config.mh_enable_quickstart = true; + config.mh_disable_combine = true; + config.mh_diversify = false; + config.local_partitioning_repetitions = 1; + + auto driver = parallel_mh_async{communicator}; + driver.perform_partitioning(config, graph); + auto const first_partition = partition_vector(graph); + auto metrics = quality_metrics{}; + auto const first_cut = metrics.edge_cut(graph); + + reset_graph_partition(graph, rank); + driver.perform_partitioning(config, graph); + auto const second_partition = partition_vector(graph); + auto const second_cut = metrics.edge_cut(graph); + + CHECK(second_partition == first_partition); + CHECK(second_cut == first_cut); + REQUIRE(PMPI_Comm_free(&communicator) == MPI_SUCCESS); +} diff --git a/parallel/parallel_src/tests/evolutionary/verify_evolutionary_failure.cmake b/parallel/parallel_src/tests/evolutionary/verify_evolutionary_failure.cmake new file mode 100644 index 00000000..e9a3a4d4 --- /dev/null +++ b/parallel/parallel_src/tests/evolutionary/verify_evolutionary_failure.cmake @@ -0,0 +1,72 @@ +cmake_minimum_required(VERSION 4.0) + +if( + NOT DEFINED MPIEXEC_EXECUTABLE + OR NOT DEFINED MPIEXEC_NUMPROC_FLAG + OR NOT DEFINED PROBE + OR NOT DEFINED MODE + OR NOT DEFINED EXPECTED_DIAGNOSTIC +) + message( + FATAL_ERROR + "MPI launcher, PROBE, MODE, and EXPECTED_DIAGNOSTIC are required" + ) +endif() + +execute_process( + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 2 + ${MPIEXEC_PREFLAGS} + "${PROBE}" "${MODE}" + ${MPIEXEC_POSTFLAGS} + RESULT_VARIABLE probe_result + OUTPUT_VARIABLE probe_stdout + ERROR_VARIABLE probe_stderr + TIMEOUT 15 +) + +set(probe_output "${probe_stdout}\n${probe_stderr}") +if("${probe_result}" STREQUAL "0") + message(FATAL_ERROR "failure probe returned success\n${probe_output}") +endif() +if("${probe_result}" MATCHES "[Tt]imeout") + message(FATAL_ERROR "failure probe timed out\n${probe_output}") +endif() + +string( + REGEX MATCHALL + "observed evolutionary lifetime MPI_Abort on affected communicator" + abort_markers + "${probe_output}" +) +list(LENGTH abort_markers abort_count) +if(NOT DEFINED EXPECTED_ABORT_COUNT) + set(EXPECTED_ABORT_COUNT 2) +endif() +if(NOT abort_count EQUAL EXPECTED_ABORT_COUNT) + message( + FATAL_ERROR + "expected exactly ${EXPECTED_ABORT_COUNT} affected-communicator abort markers; found ${abort_count}\n${probe_output}" + ) +endif() + +string(FIND "${probe_output}" "${EXPECTED_DIAGNOSTIC}" diagnostic_offset) +if(diagnostic_offset EQUAL -1) + message( + FATAL_ERROR + "missing evolutionary lifetime failure diagnostic '${EXPECTED_DIAGNOSTIC}'\n${probe_output}" + ) +endif() + +foreach( + forbidden + IN ITEMS + "unexpected state" + "returned without fail-fast" + "MPI_Finalize" +) + string(FIND "${probe_output}" "${forbidden}" forbidden_offset) + if(NOT forbidden_offset EQUAL -1) + message(FATAL_ERROR "failure probe used ${forbidden}\n${probe_output}") + endif() +endforeach() diff --git a/parallel/parallel_src/tests/fixtures/cube_fixture_cli.cmake b/parallel/parallel_src/tests/fixtures/cube_fixture_cli.cmake new file mode 100644 index 00000000..08ba73fe --- /dev/null +++ b/parallel/parallel_src/tests/fixtures/cube_fixture_cli.cmake @@ -0,0 +1,53 @@ +cmake_minimum_required(VERSION 4.0) + +foreach(required IN ITEMS GENERATOR VERIFIER WORK_DIRECTORY) + if(NOT DEFINED ${required}) + message(FATAL_ERROR "${required} is required") + endif() +endforeach() + +file(MAKE_DIRECTORY "${WORK_DIRECTORY}") +set(graph_path "${WORK_DIRECTORY}/cube-2x2x1.graph") +set(partition_path "${WORK_DIRECTORY}/cube-2x2x1.txtp") + +execute_process( + COMMAND "${GENERATOR}" 2 2 1 "${graph_path}" + RESULT_VARIABLE generator_result + OUTPUT_VARIABLE generator_stdout + ERROR_VARIABLE generator_stderr +) +if(NOT generator_result EQUAL 0) + message( + FATAL_ERROR + "cube generator failed\n${generator_stdout}\n${generator_stderr}" + ) +endif() + +file(READ "${graph_path}" graph_contents) +set(expected_graph "4 4\n2 3\n1 4\n1 4\n2 3\n") +if(NOT graph_contents STREQUAL expected_graph) + message(FATAL_ERROR "cube generator produced noncanonical adjacency") +endif() + +file(WRITE "${partition_path}" "0\n0\n1\n1\n") +execute_process( + COMMAND "${VERIFIER}" 2 2 1 2 0 "${partition_path}" + RESULT_VARIABLE verifier_result + OUTPUT_VARIABLE verifier_stdout + ERROR_VARIABLE verifier_stderr +) +if(NOT verifier_result EQUAL 0) + message( + FATAL_ERROR + "cube verifier failed\n${verifier_stdout}\n${verifier_stderr}" + ) +endif() +foreach(expected IN ITEMS "block-weights=[2,2]" "weighted-cut=2") + string(FIND "${verifier_stdout}" "${expected}" expected_index) + if(expected_index EQUAL -1) + message( + FATAL_ERROR + "cube verifier omitted '${expected}'\n${verifier_stdout}" + ) + endif() +endforeach() diff --git a/parallel/parallel_src/tests/fixtures/cube_graph.cpp b/parallel/parallel_src/tests/fixtures/cube_graph.cpp new file mode 100644 index 00000000..5ca5534e --- /dev/null +++ b/parallel/parallel_src/tests/fixtures/cube_graph.cpp @@ -0,0 +1,124 @@ +#include "fixtures/cube_graph.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace parhip::testing { +namespace { +using count_type = std::uint64_t; + +[[nodiscard]] auto checked_multiply(count_type left, + count_type right, + std::string_view quantity) -> count_type { + if (left != 0 && right > std::numeric_limits::max() / left) { + throw std::overflow_error{std::string{quantity}}; + } + return left * right; +} + +[[nodiscard]] auto checked_add(count_type left, + count_type right, + std::string_view quantity) -> count_type { + if (right > std::numeric_limits::max() - left) { + throw std::overflow_error{std::string{quantity}}; + } + return left + right; +} + +[[nodiscard]] auto axis_edge_count(count_type length, + count_type other_first, + count_type other_second) -> count_type { + return checked_multiply( + checked_multiply(length - 1, other_first, "cube edge count overflow"), + other_second, "cube edge count overflow"); +} +} // namespace + +cube_graph::cube_graph(cube_dimensions dimensions) + : dimensions_(dimensions), + xy_plane_(0), + vertex_count_(0), + undirected_edge_count_(0) { + if (dimensions.nx == 0 || dimensions.ny == 0 || dimensions.nz == 0) { + throw std::invalid_argument{"cube dimensions must all be positive"}; + } + + xy_plane_ = checked_multiply(dimensions.nx, dimensions.ny, + "cube vertex count overflow"); + vertex_count_ = + checked_multiply(xy_plane_, dimensions.nz, "cube vertex count overflow"); + undirected_edge_count_ = checked_add( + checked_add(axis_edge_count(dimensions.nx, dimensions.ny, dimensions.nz), + axis_edge_count(dimensions.ny, dimensions.nx, dimensions.nz), + "cube edge count overflow"), + axis_edge_count(dimensions.nz, dimensions.nx, dimensions.ny), + "cube edge count overflow"); +} + +auto cube_graph::cell_id(std::uint64_t x, + std::uint64_t y, + std::uint64_t z) const -> vertex_id { + if (x >= dimensions_.nx || y >= dimensions_.ny || z >= dimensions_.nz) { + throw std::out_of_range{"cube cell coordinate is outside the graph"}; + } + return x + dimensions_.nx * (y + dimensions_.ny * z); +} + +auto cube_graph::neighbors(vertex_id vertex) const -> cube_neighbors { + if (vertex >= vertex_count_) { + throw std::out_of_range{"cube vertex is outside the graph"}; + } + + auto result = cube_neighbors{}; + auto const z = vertex / xy_plane_; + auto const within_plane = vertex % xy_plane_; + auto const y = within_plane / dimensions_.nx; + auto const x = within_plane % dimensions_.nx; + auto append = [&result](vertex_id neighbor) { + result.values_[result.size_++] = neighbor; + }; + + if (x > 0) { + append(vertex - 1); + } + if (x + 1 < dimensions_.nx) { + append(vertex + 1); + } + if (y > 0) { + append(vertex - dimensions_.nx); + } + if (y + 1 < dimensions_.ny) { + append(vertex + dimensions_.nx); + } + if (z > 0) { + append(vertex - xy_plane_); + } + if (z + 1 < dimensions_.nz) { + append(vertex + xy_plane_); + } + + std::ranges::sort(std::span{result.values_.data(), result.size_}); + return result; +} + +void cube_graph::write_metis(std::ostream& output) const { + output << vertex_count_ << ' ' << undirected_edge_count_ << '\n'; + for (auto vertex = vertex_id{0}; vertex < vertex_count_; ++vertex) { + auto const adjacent = neighbors(vertex); + auto separator = std::string_view{}; + for (auto const neighbor : adjacent) { + output << separator << neighbor + 1; + separator = " "; + } + output << '\n'; + } + if (!output) { + throw std::runtime_error{"failed to write cube graph"}; + } +} +} // namespace parhip::testing diff --git a/parallel/parallel_src/tests/fixtures/cube_graph.h b/parallel/parallel_src/tests/fixtures/cube_graph.h new file mode 100644 index 00000000..883a8ac6 --- /dev/null +++ b/parallel/parallel_src/tests/fixtures/cube_graph.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace parhip::testing { +struct cube_dimensions final { + std::uint64_t nx; + std::uint64_t ny; + std::uint64_t nz; +}; + +class cube_neighbors final { + public: + using value_type = std::uint64_t; + using const_iterator = value_type const*; + + [[nodiscard]] auto begin() const noexcept -> const_iterator { + return values_.data(); + } + [[nodiscard]] auto end() const noexcept -> const_iterator { + return values_.data() + size_; + } + [[nodiscard]] auto size() const noexcept -> std::size_t { return size_; } + [[nodiscard]] auto values() const noexcept -> std::span { + return {begin(), end()}; + } + + private: + friend class cube_graph; + + std::array values_{}; + std::size_t size_{}; +}; + +class cube_graph final { + public: + using vertex_id = std::uint64_t; + + explicit cube_graph(cube_dimensions dimensions); + + [[nodiscard]] auto dimensions() const noexcept -> cube_dimensions { + return dimensions_; + } + [[nodiscard]] auto vertex_count() const noexcept -> std::uint64_t { + return vertex_count_; + } + [[nodiscard]] auto undirected_edge_count() const noexcept -> std::uint64_t { + return undirected_edge_count_; + } + [[nodiscard]] auto cell_id(std::uint64_t x, + std::uint64_t y, + std::uint64_t z) const -> vertex_id; + [[nodiscard]] auto neighbors(vertex_id vertex) const -> cube_neighbors; + + void write_metis(std::ostream& output) const; + + private: + cube_dimensions dimensions_; + std::uint64_t xy_plane_; + std::uint64_t vertex_count_; + std::uint64_t undirected_edge_count_; +}; +} // namespace parhip::testing diff --git a/parallel/parallel_src/tests/fixtures/cube_graph_generator.cpp b/parallel/parallel_src/tests/fixtures/cube_graph_generator.cpp new file mode 100644 index 00000000..ba76d5a2 --- /dev/null +++ b/parallel/parallel_src/tests/fixtures/cube_graph_generator.cpp @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "fixtures/cube_graph.h" + +namespace { +[[nodiscard]] auto parse_dimension(std::string_view text, + std::uint64_t& value) noexcept -> bool { + auto const [end, error] = + std::from_chars(text.data(), text.data() + text.size(), value); + return error == std::errc{} && end == text.data() + text.size(); +} +} // namespace + +int main(int argc, char* argv[]) { + if (argc != 5) { + std::cerr << "usage: " + << (argc > 0 ? argv[0] : "kahip_cube_generator") + << " NX NY NZ OUTPUT.graph\n"; + return 64; + } + + auto dimensions = parhip::testing::cube_dimensions{}; + if (!parse_dimension(argv[1], dimensions.nx) || + !parse_dimension(argv[2], dimensions.ny) || + !parse_dimension(argv[3], dimensions.nz)) { + std::cerr << "cube dimensions must be unsigned integers\n"; + return 64; + } + + try { + auto const graph = parhip::testing::cube_graph{dimensions}; + auto output = std::ofstream{argv[4], std::ios::out | std::ios::trunc}; + if (!output) { + std::cerr << "cannot open cube graph output '" << argv[4] << "'\n"; + return 1; + } + graph.write_metis(output); + std::cout << "generated " << graph.vertex_count() << " vertices and " + << graph.undirected_edge_count() << " undirected edges in " + << argv[4] << '\n'; + } catch (std::exception const& error) { + std::cerr << "cannot generate cube graph: " << error.what() << '\n'; + return 1; + } +} diff --git a/parallel/parallel_src/tests/fixtures/cube_graph_test.cpp b/parallel/parallel_src/tests/fixtures/cube_graph_test.cpp new file mode 100644 index 00000000..870e5201 --- /dev/null +++ b/parallel/parallel_src/tests/fixtures/cube_graph_test.cpp @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include + +#include + +#include "fixtures/cube_graph.h" + +namespace { +using parhip::testing::cube_dimensions; +using parhip::testing::cube_graph; + +[[nodiscard]] auto neighbor_ids(cube_graph const& graph, std::uint64_t vertex) { + auto const neighbors = graph.neighbors(vertex); + return std::vector{neighbors.begin(), neighbors.end()}; +} +} // namespace + +TEST_CASE("cube graph uses the documented linear cell identifier", + "[cube][fixture]") { + auto const graph = cube_graph{cube_dimensions{.nx = 3, .ny = 2, .nz = 2}}; + + REQUIRE(graph.cell_id(0, 0, 0) == 0); + REQUIRE(graph.cell_id(2, 0, 0) == 2); + REQUIRE(graph.cell_id(0, 1, 0) == 3); + REQUIRE(graph.cell_id(1, 1, 1) == 10); + REQUIRE(graph.cell_id(2, 1, 1) == 11); +} + +TEST_CASE("cube graph adjacency is the sorted six-face neighborhood", + "[cube][fixture]") { + auto const graph = cube_graph{cube_dimensions{.nx = 3, .ny = 2, .nz = 2}}; + + REQUIRE(neighbor_ids(graph, 0) == std::vector{1, 3, 6}); + REQUIRE(neighbor_ids(graph, 4) == std::vector{1, 3, 5, 10}); + REQUIRE(neighbor_ids(graph, 11) == std::vector{5, 8, 10}); +} + +TEST_CASE("cube graph counts match the CI and large debugging fixtures", + "[cube][fixture]") { + auto const four = cube_graph{cube_dimensions{.nx = 4, .ny = 4, .nz = 4}}; + auto const ten = cube_graph{cube_dimensions{.nx = 10, .ny = 10, .nz = 10}}; + auto const hundred = + cube_graph{cube_dimensions{.nx = 100, .ny = 100, .nz = 100}}; + + REQUIRE(four.vertex_count() == 64); + REQUIRE(four.undirected_edge_count() == 144); + REQUIRE(ten.vertex_count() == 1'000); + REQUIRE(ten.undirected_edge_count() == 2'700); + REQUIRE(hundred.vertex_count() == 1'000'000); + REQUIRE(hundred.undirected_edge_count() == 2'970'000); +} + +TEST_CASE("cube graph streams canonical one-based METIS adjacency", + "[cube][fixture]") { + auto const graph = cube_graph{cube_dimensions{.nx = 2, .ny = 1, .nz = 1}}; + auto output = std::ostringstream{}; + + graph.write_metis(output); + + REQUIRE(output.str() == "2 1\n2\n1\n"); +} + +TEST_CASE("cube dimensions reject empty and unrepresentable graphs", + "[cube][fixture]") { + REQUIRE_THROWS_AS(cube_graph(cube_dimensions{.nx = 0, .ny = 1, .nz = 1}), + std::invalid_argument); + REQUIRE_THROWS_AS(cube_graph(cube_dimensions{ + .nx = std::numeric_limits::max(), + .ny = 2, + .nz = 1, + }), + std::overflow_error); +} diff --git a/parallel/parallel_src/tests/fixtures/cube_partition.cpp b/parallel/parallel_src/tests/fixtures/cube_partition.cpp new file mode 100644 index 00000000..a0710369 --- /dev/null +++ b/parallel/parallel_src/tests/fixtures/cube_partition.cpp @@ -0,0 +1,113 @@ +#include "fixtures/cube_partition.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace parhip::testing { +namespace { +[[nodiscard]] auto checked_add(std::uint64_t left, + std::uint64_t right, + char const* context) -> std::uint64_t { + if (right > std::numeric_limits::max() - left) { + throw std::overflow_error{context}; + } + return left + right; +} + +[[nodiscard]] auto checked_multiply(std::uint64_t left, + std::uint64_t right, + char const* context) -> std::uint64_t { + if (left != 0 && right > std::numeric_limits::max() / left) { + throw std::overflow_error{context}; + } + return left * right; +} +} // namespace + +auto evaluate_cube_partition(cube_graph const& graph, + std::span partition, + std::uint64_t block_count, + std::uint64_t imbalance_percent) + -> cube_partition_metrics { + if (block_count == 0 || !std::in_range(graph.vertex_count()) || + !std::in_range(block_count)) { + throw std::invalid_argument{"partition dimensions are not representable"}; + } + if (!std::cmp_equal(partition.size(), graph.vertex_count())) { + throw std::invalid_argument{ + "partition must contain exactly one block per cube vertex"}; + } + + auto metrics = cube_partition_metrics{ + .block_weights = + std::vector(static_cast(block_count), 0), + .maximum_block_weight = 0, + .weighted_cut = 0, + }; + for (auto const block : partition) { + if (block >= block_count) { + throw std::runtime_error{"partition contains an invalid block"}; + } + ++metrics.block_weights[static_cast(block)]; + } + + auto const ideal_block_weight = + graph.vertex_count() / block_count + + static_cast(graph.vertex_count() % block_count != 0); + auto const percent_scale = + checked_add(100, imbalance_percent, "partition imbalance scale overflow"); + metrics.maximum_block_weight = + checked_multiply(ideal_block_weight, percent_scale, + "partition balance bound overflow") / + 100; + if (std::ranges::any_of(metrics.block_weights, [&](auto weight) { + return weight > metrics.maximum_block_weight; + })) { + throw std::runtime_error{"partition violates the block-weight bound"}; + } + + for (auto vertex = cube_graph::vertex_id{0}; vertex < graph.vertex_count(); + ++vertex) { + auto const source_block = partition[static_cast(vertex)]; + for (auto const neighbor : graph.neighbors(vertex)) { + if (neighbor > vertex && + partition[static_cast(neighbor)] != source_block) { + ++metrics.weighted_cut; + } + } + } + return metrics; +} + +auto read_text_partition(std::istream& input, std::uint64_t expected_vertices) + -> std::vector { + if (!std::in_range(expected_vertices)) { + throw std::overflow_error{"partition length is not locally representable"}; + } + + auto partition = std::vector{}; + partition.reserve(static_cast(expected_vertices)); + for (auto index = std::uint64_t{0}; index < expected_vertices; ++index) { + auto block = std::uint64_t{}; + if (!(input >> block)) { + throw std::runtime_error{"partition is missing or has a malformed block"}; + } + partition.push_back(block); + } + + auto trailing = std::string{}; + if (input >> trailing) { + throw std::runtime_error{"partition has more blocks than vertices"}; + } + if (!input.eof()) { + throw std::runtime_error{"partition has malformed trailing data"}; + } + return partition; +} +} // namespace parhip::testing diff --git a/parallel/parallel_src/tests/fixtures/cube_partition.h b/parallel/parallel_src/tests/fixtures/cube_partition.h new file mode 100644 index 00000000..12323685 --- /dev/null +++ b/parallel/parallel_src/tests/fixtures/cube_partition.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include +#include + +#include "fixtures/cube_graph.h" + +namespace parhip::testing { +struct cube_partition_metrics final { + std::vector block_weights; + std::uint64_t maximum_block_weight; + std::uint64_t weighted_cut; +}; + +[[nodiscard]] auto evaluate_cube_partition( + cube_graph const& graph, + std::span partition, + std::uint64_t block_count, + std::uint64_t imbalance_percent) -> cube_partition_metrics; + +[[nodiscard]] auto read_text_partition(std::istream& input, + std::uint64_t expected_vertices) + -> std::vector; +} // namespace parhip::testing diff --git a/parallel/parallel_src/tests/fixtures/cube_partition_oracle.cmake b/parallel/parallel_src/tests/fixtures/cube_partition_oracle.cmake new file mode 100644 index 00000000..2141a2a9 --- /dev/null +++ b/parallel/parallel_src/tests/fixtures/cube_partition_oracle.cmake @@ -0,0 +1,490 @@ +cmake_minimum_required(VERSION 4.0) + +foreach(required IN ITEMS + GENERATOR + PARHIP + VERIFIER + MPIEXEC_EXECUTABLE + MPIEXEC_NUMPROC_FLAG + MANIFEST + WORK_DIRECTORY + FIXTURE + NX + NY + NZ + BLOCKS + RANKS) + if(NOT DEFINED ${required}) + message(FATAL_ERROR "${required} is required") + endif() +endforeach() + +foreach(required_file IN ITEMS GENERATOR PARHIP VERIFIER MANIFEST) + if(NOT EXISTS "${${required_file}}") + message(FATAL_ERROR + "${required_file} does not exist: ${${required_file}}" + ) + endif() +endforeach() + +file(STRINGS "${MANIFEST}" manifest_lines ENCODING UTF-8) +list(POP_FRONT manifest_lines manifest_title) +if(NOT manifest_title STREQUAL "KaHIP cube partition oracle provenance") + message(FATAL_ERROR "invalid cube oracle provenance title") +endif() + +set(provenance_keys + upstream_revision + upstream_compiler + upstream_mpi + cell_id + adjacency +) +set(repair_provenance_keys + repair_semantics + repair_revision + repair_compiler + repair_mpi +) +set(manifest_keys "") +foreach(line IN LISTS manifest_lines) + if(line STREQUAL "") + continue() + endif() + + string(FIND "${line}" "=" separator) + if(separator LESS 1) + message(FATAL_ERROR + "malformed cube oracle manifest line '${line}'" + ) + endif() + string(SUBSTRING "${line}" 0 ${separator} key) + math(EXPR value_begin "${separator} + 1") + string(SUBSTRING "${line}" ${value_begin} -1 value) + if(value STREQUAL "") + message(FATAL_ERROR + "malformed cube oracle manifest line '${line}'" + ) + endif() + + list(FIND manifest_keys "${key}" duplicate_index) + if(NOT duplicate_index EQUAL -1) + message(FATAL_ERROR "duplicate cube oracle key '${key}'") + endif() + list(APPEND manifest_keys "${key}") + + if(key IN_LIST provenance_keys) + continue() + endif() + if(key IN_LIST repair_provenance_keys) + continue() + endif() + if(key MATCHES + "^[A-Za-z][A-Za-z0-9_-]*\\.(nx|ny|nz|blocks|preconfiguration|seed|imbalance_percent|graph_file_sha256)$") + continue() + endif() + if(key MATCHES + "^[A-Za-z][A-Za-z0-9_-]*\\.rank[1-9][0-9]*\\.(partition_file_sha256|partition_sha256|partition|block_weights|weighted_cut)$") + continue() + endif() + if(key MATCHES + "^[A-Za-z][A-Za-z0-9_-]*\\.rank[1-9][0-9]*\\.repaired_(upstream_partition_sha256|partition_file_sha256|partition_sha256|partition|block_weights|weighted_cut)$") + continue() + endif() + message(FATAL_ERROR "unknown cube oracle manifest key '${key}'") +endforeach() + +function(manifest_find key output found_output) + set(found FALSE) + set(value "") + foreach(line IN LISTS manifest_lines) + string(FIND "${line}" "=" separator) + if(separator LESS 1) + continue() + endif() + string(SUBSTRING "${line}" 0 ${separator} candidate_key) + if(NOT candidate_key STREQUAL key) + continue() + endif() + if(found) + message(FATAL_ERROR "duplicate cube oracle key '${key}'") + endif() + math(EXPR value_begin "${separator} + 1") + string(SUBSTRING "${line}" ${value_begin} -1 value) + set(found TRUE) + endforeach() + set(${output} "${value}" PARENT_SCOPE) + set(${found_output} ${found} PARENT_SCOPE) +endfunction() + +function(manifest_require key output) + manifest_find("${key}" value found) + if(NOT found) + message(FATAL_ERROR "cube oracle manifest is missing '${key}'") + endif() + set(${output} "${value}" PARENT_SCOPE) +endfunction() + +function(require_lower_hex name value length) + string(LENGTH "${value}" actual_length) + if(NOT actual_length EQUAL length OR NOT value MATCHES "^[0-9a-f]+$") + message(FATAL_ERROR + "${name} must be ${length} lowercase hexadecimal characters" + ) + endif() +endfunction() + +manifest_require(upstream_revision upstream_revision) +require_lower_hex("upstream revision" "${upstream_revision}" 40) +manifest_require(upstream_compiler upstream_compiler) +if(NOT upstream_compiler MATCHES + "^[A-Za-z][A-Za-z0-9+._-]*-[0-9]+(\\.[0-9]+)+$") + message(FATAL_ERROR + "invalid upstream compiler provenance '${upstream_compiler}'" + ) +endif() +manifest_require(upstream_mpi upstream_mpi) +if(NOT upstream_mpi MATCHES + "^[A-Za-z][A-Za-z0-9+._-]*-[0-9]+(\\.[0-9]+)+-MPI-[0-9]+\\.[0-9]+$") + message(FATAL_ERROR "invalid upstream MPI provenance '${upstream_mpi}'") +endif() +manifest_require(cell_id cell_id_recipe) +if(NOT cell_id_recipe STREQUAL "x+nx*(y+ny*z)") + message(FATAL_ERROR "unsupported cube cell-id recipe '${cell_id_recipe}'") +endif() +manifest_require(adjacency adjacency_recipe) +if(NOT adjacency_recipe STREQUAL "sorted-six-face-neighborhood") + message(FATAL_ERROR + "unsupported cube adjacency recipe '${adjacency_recipe}'" + ) +endif() + +set(repair_tuples "") +foreach(key IN LISTS manifest_keys) + if(key MATCHES + "^([A-Za-z][A-Za-z0-9_-]*\\.rank[1-9][0-9]*)\\.repaired_") + list(APPEND repair_tuples "${CMAKE_MATCH_1}") + endif() +endforeach() +list(REMOVE_DUPLICATES repair_tuples) +set(repair_provenance_present FALSE) +foreach(key IN LISTS repair_provenance_keys) + list(FIND manifest_keys "${key}" repair_provenance_index) + if(NOT repair_provenance_index EQUAL -1) + set(repair_provenance_present TRUE) + endif() +endforeach() +if(repair_provenance_present AND NOT repair_tuples) + message(FATAL_ERROR + "cube oracle repair provenance has no repair overlay" + ) +endif() +if(repair_tuples) + foreach(key IN LISTS repair_provenance_keys) + manifest_find("${key}" repair_${key} has_repair_${key}) + if(NOT has_repair_${key}) + message(FATAL_ERROR + "cube oracle repair provenance is missing '${key}'" + ) + endif() + endforeach() + if(NOT repair_repair_semantics STREQUAL "weighted-feasibility") + message(FATAL_ERROR + "unsupported repair semantics '${repair_repair_semantics}'" + ) + endif() + require_lower_hex("repair revision" "${repair_repair_revision}" 40) + if(NOT repair_repair_revision STREQUAL + "8b26fa29dece9e268c98106c315e47fdbeaf1c1b") + message(FATAL_ERROR + "unsupported repair revision '${repair_repair_revision}'" + ) + endif() + if(NOT repair_repair_compiler MATCHES + "^[A-Za-z][A-Za-z0-9+._-]*-[0-9]+(\\.[0-9]+)+$") + message(FATAL_ERROR + "invalid repair compiler provenance '${repair_repair_compiler}'" + ) + endif() + if(NOT repair_repair_compiler STREQUAL "GNU-16.2.1") + message(FATAL_ERROR + "unsupported repair compiler '${repair_repair_compiler}'" + ) + endif() + if(NOT repair_repair_mpi MATCHES + "^[A-Za-z][A-Za-z0-9+._-]*-[0-9]+(\\.[0-9]+)+-MPI-[0-9]+\\.[0-9]+$") + message(FATAL_ERROR + "invalid repair MPI provenance '${repair_repair_mpi}'" + ) + endif() + if(NOT repair_repair_mpi STREQUAL "MPICH-5.0.1-MPI-5.0") + message(FATAL_ERROR + "unsupported repair MPI '${repair_repair_mpi}'" + ) + endif() + + set(allowed_repair_tuples cube4.rank5 cube10.rank2 cube10.rank4) + foreach(repair_tuple IN LISTS repair_tuples) + if(NOT repair_tuple IN_LIST allowed_repair_tuples) + message(FATAL_ERROR + "repair overlay is not allowed for '${repair_tuple}'" + ) + endif() + foreach(field IN ITEMS + upstream_partition_sha256 + partition_file_sha256 + partition_sha256 + block_weights + weighted_cut) + manifest_require( + "${repair_tuple}.repaired_${field}" + "${repair_tuple}_repaired_${field}" + ) + endforeach() + require_lower_hex( + "${repair_tuple} repaired upstream partition SHA-256" + "${${repair_tuple}_repaired_upstream_partition_sha256}" 64 + ) + require_lower_hex( + "${repair_tuple} repaired partition file SHA-256" + "${${repair_tuple}_repaired_partition_file_sha256}" 64 + ) + require_lower_hex( + "${repair_tuple} repaired partition SHA-256" + "${${repair_tuple}_repaired_partition_sha256}" 64 + ) + if(NOT "${${repair_tuple}_repaired_block_weights}" MATCHES + "^[0-9]+(,[0-9]+)*$") + message(FATAL_ERROR + "${repair_tuple}.repaired_block_weights is not a canonical integer list" + ) + endif() + if(NOT "${${repair_tuple}_repaired_weighted_cut}" MATCHES "^[0-9]+$") + message(FATAL_ERROR + "${repair_tuple}.repaired_weighted_cut is not an unsigned integer" + ) + endif() + manifest_require( + "${repair_tuple}.partition_sha256" + "${repair_tuple}_upstream_partition_sha256" + ) + if(NOT "${${repair_tuple}_repaired_upstream_partition_sha256}" STREQUAL + "${${repair_tuple}_upstream_partition_sha256}") + message(FATAL_ERROR + "${repair_tuple} repaired upstream partition SHA-256 does not match its pristine upstream record" + ) + endif() + endforeach() + foreach(allowed_repair_tuple IN LISTS allowed_repair_tuples) + if(NOT allowed_repair_tuple IN_LIST repair_tuples) + message(FATAL_ERROR + "cube oracle manifest is missing repair overlay '${allowed_repair_tuple}'" + ) + endif() + endforeach() +endif() + +foreach(field IN ITEMS nx ny nz blocks) + manifest_require("${FIXTURE}.${field}" manifest_${field}) +endforeach() +set(manifest_dimension_fields nx ny nz blocks) +set(argument_dimension_fields NX NY NZ BLOCKS) +foreach(field expected IN ZIP_LISTS + manifest_dimension_fields + argument_dimension_fields) + if(NOT manifest_${field} STREQUAL "${${expected}}") + message(FATAL_ERROR + "${FIXTURE}.${field} is ${manifest_${field}}, expected ${${expected}}" + ) + endif() +endforeach() + +manifest_require("${FIXTURE}.preconfiguration" preconfiguration) +manifest_require("${FIXTURE}.seed" seed) +manifest_require("${FIXTURE}.imbalance_percent" imbalance_percent) +if(NOT preconfiguration STREQUAL "fastmesh") + message(FATAL_ERROR + "${FIXTURE}.preconfiguration is '${preconfiguration}', expected 'fastmesh'" + ) +endif() +if(NOT seed STREQUAL "1") + message(FATAL_ERROR "${FIXTURE}.seed is ${seed}, expected 1") +endif() +if(NOT imbalance_percent STREQUAL "3") + message(FATAL_ERROR + "${FIXTURE}.imbalance_percent is ${imbalance_percent}, expected 3" + ) +endif() +manifest_require("${FIXTURE}.graph_file_sha256" expected_graph_file_sha256) +require_lower_hex( + "graph file SHA-256" "${expected_graph_file_sha256}" 64 +) + +set(tuple "${FIXTURE}.rank${RANKS}") +manifest_require("${tuple}.partition_file_sha256" expected_file_sha256) +manifest_require("${tuple}.partition_sha256" expected_partition_sha256) +manifest_require("${tuple}.block_weights" expected_block_weights) +manifest_require("${tuple}.weighted_cut" expected_weighted_cut) +require_lower_hex("partition file SHA-256" "${expected_file_sha256}" 64) +require_lower_hex("partition SHA-256" "${expected_partition_sha256}" 64) +if(NOT expected_block_weights MATCHES "^[0-9]+(,[0-9]+)*$") + message(FATAL_ERROR + "${tuple}.block_weights is not a canonical integer list" + ) +endif() +if(NOT expected_weighted_cut MATCHES "^[0-9]+$") + message(FATAL_ERROR "${tuple}.weighted_cut is not an unsigned integer") +endif() + +set(oracle_semantics "pristine upstream") +set(exact_partition_key "${tuple}.partition") +if(tuple IN_LIST repair_tuples) + set(oracle_semantics "repaired weighted-feasibility") + set(expected_file_sha256 + "${${tuple}_repaired_partition_file_sha256}" + ) + set(expected_partition_sha256 + "${${tuple}_repaired_partition_sha256}" + ) + set(expected_block_weights "${${tuple}_repaired_block_weights}") + set(expected_weighted_cut "${${tuple}_repaired_weighted_cut}") + set(exact_partition_key "${tuple}.repaired_partition") +endif() + +file(MAKE_DIRECTORY "${WORK_DIRECTORY}") +set(graph_path "${WORK_DIRECTORY}/${FIXTURE}.graph") +set(partition_path "${WORK_DIRECTORY}/tmppartition.txtp") +file(REMOVE "${graph_path}" "${partition_path}") + +execute_process( + COMMAND "${GENERATOR}" ${NX} ${NY} ${NZ} "${graph_path}" + RESULT_VARIABLE generator_result + OUTPUT_VARIABLE generator_output + ERROR_VARIABLE generator_error +) +if(NOT generator_result EQUAL 0) + message(FATAL_ERROR + "cube generator failed (${generator_result})\n" + "${generator_output}${generator_error}" + ) +endif() +file(SHA256 "${graph_path}" actual_graph_file_sha256) +if(NOT WIN32 AND NOT actual_graph_file_sha256 STREQUAL + expected_graph_file_sha256) + message(FATAL_ERROR + "generated graph SHA-256 is ${actual_graph_file_sha256}, expected ${expected_graph_file_sha256}" + ) +endif() + +execute_process( + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" ${RANKS} + ${MPIEXEC_PREFLAGS} + "${PARHIP}" + ${MPIEXEC_POSTFLAGS} + "${graph_path}" + "--k=${BLOCKS}" + "--preconfiguration=${preconfiguration}" + "--seed=${seed}" + "--imbalance=${imbalance_percent}" + --save_partition + WORKING_DIRECTORY "${WORK_DIRECTORY}" + RESULT_VARIABLE partition_result + OUTPUT_VARIABLE partition_output + ERROR_VARIABLE partition_error +) +if(NOT partition_result EQUAL 0) + message(FATAL_ERROR + "ParHIP cube oracle run failed (${partition_result})\n" + "${partition_output}${partition_error}" + ) +endif() +if(NOT EXISTS "${partition_path}") + message(FATAL_ERROR "ParHIP did not produce ${partition_path}") +endif() + +file(STRINGS "${partition_path}" partition ENCODING UTF-8) +math(EXPR expected_vertices "${NX} * ${NY} * ${NZ}") +list(LENGTH partition actual_vertices) +if(NOT actual_vertices EQUAL expected_vertices) + message(FATAL_ERROR + "partition contains ${actual_vertices} vertices, expected ${expected_vertices}" + ) +endif() +list(JOIN partition "," canonical_partition) +string(SHA256 actual_partition_sha256 "${canonical_partition}") +if(NOT actual_partition_sha256 STREQUAL expected_partition_sha256) + message(FATAL_ERROR + "canonical partition SHA-256 is ${actual_partition_sha256}, expected ${expected_partition_sha256}" + ) +endif() + +file(SHA256 "${partition_path}" actual_file_sha256) +if(NOT WIN32 AND NOT actual_file_sha256 STREQUAL expected_file_sha256) + message(FATAL_ERROR + "partition file SHA-256 is ${actual_file_sha256}, expected ${expected_file_sha256}" + ) +endif() + +manifest_find("${exact_partition_key}" exact_partition has_exact_partition) +if(has_exact_partition AND NOT canonical_partition STREQUAL exact_partition) + message(FATAL_ERROR "${tuple} differs from its exact ${oracle_semantics} vector") +endif() + +execute_process( + COMMAND + "${VERIFIER}" ${NX} ${NY} ${NZ} ${BLOCKS} + ${imbalance_percent} "${partition_path}" + RESULT_VARIABLE verifier_result + OUTPUT_VARIABLE verifier_output + ERROR_VARIABLE verifier_error +) +if(NOT verifier_result EQUAL 0) + message(FATAL_ERROR + "cube partition invariant verification failed (${verifier_result})\n" + "${verifier_output}${verifier_error}" + ) +endif() +string(STRIP "${verifier_output}" verifier_record) +set(verifier_record_pattern + "^verified vertices=([0-9]+) blocks=([0-9]+) maximum-block-weight=[0-9]+ block-weights=\\[([0-9,]+)\\] weighted-cut=([0-9]+)$" +) +if(NOT verifier_record MATCHES "${verifier_record_pattern}") + message(FATAL_ERROR + "cube verifier produced a malformed result record\n${verifier_output}" + ) +endif() +set(actual_verifier_vertices "${CMAKE_MATCH_1}") +set(actual_verifier_blocks "${CMAKE_MATCH_2}") +set(actual_block_weights "${CMAKE_MATCH_3}") +set(actual_weighted_cut "${CMAKE_MATCH_4}") +if(NOT actual_verifier_vertices STREQUAL "${expected_vertices}") + message(FATAL_ERROR + "verified vertex count is ${actual_verifier_vertices}, expected ${expected_vertices}" + ) +endif() +if(NOT actual_verifier_blocks STREQUAL "${BLOCKS}") + message(FATAL_ERROR + "verified block count is ${actual_verifier_blocks}, expected ${BLOCKS}" + ) +endif() +if(NOT actual_block_weights STREQUAL expected_block_weights) + message(FATAL_ERROR + "block weights are [${actual_block_weights}], expected [${expected_block_weights}]" + ) +endif() +if(NOT actual_weighted_cut STREQUAL expected_weighted_cut) + message(FATAL_ERROR + "weighted cut is ${actual_weighted_cut}, expected ${expected_weighted_cut}" + ) +endif() + +if(oracle_semantics STREQUAL "repaired weighted-feasibility") + message(STATUS + "verified ${tuple} against repaired weighted-feasibility semantics: repair=${repair_repair_revision} compiler=${repair_repair_compiler} mpi=${repair_repair_mpi} upstream-anchor=${${tuple}_repaired_upstream_partition_sha256} partition=${actual_partition_sha256} cut=${expected_weighted_cut}" + ) +else() + message(STATUS + "verified ${tuple} against upstream ${upstream_revision}: partition=${actual_partition_sha256} cut=${expected_weighted_cut}" + ) +endif() diff --git a/parallel/parallel_src/tests/fixtures/cube_partition_oracle.txt b/parallel/parallel_src/tests/fixtures/cube_partition_oracle.txt new file mode 100644 index 00000000..f5ba3f4f --- /dev/null +++ b/parallel/parallel_src/tests/fixtures/cube_partition_oracle.txt @@ -0,0 +1,96 @@ +KaHIP cube partition oracle provenance +upstream_revision=5935f349f65f1788a9b68fcf6d853e698d86956d +upstream_compiler=GNU-16.2.1 +upstream_mpi=Open-MPI-5.0.10-MPI-3.1 +cell_id=x+nx*(y+ny*z) +adjacency=sorted-six-face-neighborhood + +repair_semantics=weighted-feasibility +repair_revision=8b26fa29dece9e268c98106c315e47fdbeaf1c1b +repair_compiler=GNU-16.2.1 +repair_mpi=MPICH-5.0.1-MPI-5.0 + +cube4.nx=4 +cube4.ny=4 +cube4.nz=4 +cube4.blocks=2 +cube4.preconfiguration=fastmesh +cube4.seed=1 +cube4.imbalance_percent=3 +cube4.graph_file_sha256=00901f7493e635c9ce469e4bea6f91d0ae45dcc0d4bae5d197845066fe092b67 +cube4.rank1.partition_file_sha256=bc6b979c91853cce9469c590672114e304830f53882a606580dd10484afa33bf +cube4.rank1.partition_sha256=ffd36803272dadd052074e0c776743a60f2962bedd528da546ccda0c40ced4ca +cube4.rank1.partition=0,0,0,0,0,0,0,0,1,1,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,1,0,0,0,1,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1 +cube4.rank1.block_weights=32,32 +cube4.rank1.weighted_cut=28 +cube4.rank2.partition_file_sha256=cbe1f85fcc3382931effe4e5902d59cebe9ea5493d4ac240680e99fea52ca83b +cube4.rank2.partition_sha256=1d586d31cce988bfca06ba1afea96144f2e75e60ae5605c2380b16a86c0706f1 +cube4.rank2.partition=1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +cube4.rank2.block_weights=32,32 +cube4.rank2.weighted_cut=16 +cube4.rank3.partition_file_sha256=9c0416edf265e8bc59e8de487f7c787b559f2bff49d977f4eee905ee365be189 +cube4.rank3.partition_sha256=ce5d2706c8289ea6cd694d01e77aebf6fb2c1d9df7b53fb3e5ca88d53b777748 +cube4.rank3.partition=1,1,1,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,1,1,0,0,0,1,0,0,0,1,0,0,1,1,1,0,1,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1 +cube4.rank3.block_weights=32,32 +cube4.rank3.weighted_cut=31 +cube4.rank4.partition_file_sha256=e701fbe8b8db816d383e59493210e195288e7e89bc545ded570e6b9b8d1729c4 +cube4.rank4.partition_sha256=f2090b57eff4bbe83bff8d9a2f52b875c0ebca6b550967f879b9d6da705e3aa8 +cube4.rank4.partition=0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 +cube4.rank4.block_weights=32,32 +cube4.rank4.weighted_cut=16 +cube4.rank5.partition_file_sha256=417d036c220f0c820101692a0223f3f307d1c1e5b68cd569116c532fb04ca3a5 +cube4.rank5.partition_sha256=05d39781dac8fc7e376085023430956bd3b63e14b97ce44b59334aee059cc85a +cube4.rank5.partition=0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 +cube4.rank5.block_weights=32,32 +cube4.rank5.weighted_cut=22 + +cube10.nx=10 +cube10.ny=10 +cube10.nz=10 +cube10.blocks=4 +cube10.preconfiguration=fastmesh +cube10.seed=1 +cube10.imbalance_percent=3 +cube10.graph_file_sha256=2a2de2d8eeb60cfb7c8c15902c77a8c90af954a0e35cf8560ac8205dbebdbdec +cube10.rank2.partition_file_sha256=671609388c1d1d517147bb89838aec6c3a9e06e7ffc258ce29c0c1afe2b36b7d +cube10.rank2.partition_sha256=9a362041172c39f4b17d68b6919dd43e73260d7aa4bfec633f29924e81396817 +cube10.rank2.block_weights=245,253,251,251 +cube10.rank2.weighted_cut=248 +cube10.rank4.partition_file_sha256=388e9552ef1aabf9fdac660f9004fbb30b4e2386ab02b1d9284de25488813e89 +cube10.rank4.partition_sha256=0b200061d7cabead1743692b4055fd1beb2ef833239ded2342020b844df872c1 +cube10.rank4.block_weights=233,257,254,256 +cube10.rank4.weighted_cut=254 + +cube100.nx=100 +cube100.ny=100 +cube100.nz=100 +cube100.blocks=4 +cube100.preconfiguration=fastmesh +cube100.seed=1 +cube100.imbalance_percent=3 +cube100.graph_file_sha256=bcaae8173e0a941a4800ba751bdfd95dcd603cd558319792a3410cbb73e99deb +cube100.rank2.partition_file_sha256=219c2043da5a290f98e8afde9271a2eb3db323e03cb4d7c66705991b22f01d23 +cube100.rank2.partition_sha256=8d418102042c43bb0ac07a32e4b1309dd4dd622bc466d00100a3b1a73ab429b9 +cube100.rank2.block_weights=255107,240681,251234,252978 +cube100.rank2.weighted_cut=31767 +cube100.rank4.partition_file_sha256=6f5c0069e586c649349f418bbc7ff6b63ebcb9a062692971d2ad328a1a68453a +cube100.rank4.partition_sha256=5b9d8e0da1c30f024b29a997e55b08330acdea00b6ad371db95dd60bde88907b +cube100.rank4.block_weights=256892,257365,241889,243854 +cube100.rank4.weighted_cut=30370 + +cube4.rank5.repaired_upstream_partition_sha256=05d39781dac8fc7e376085023430956bd3b63e14b97ce44b59334aee059cc85a +cube4.rank5.repaired_partition_file_sha256=01c9a40218fd770ddd728e0252267d990277d834259c85539c8924228bb584fb +cube4.rank5.repaired_partition_sha256=27d46f0e3a6255f9bde5ff016f9fd6c910f5839af042012970497a2abc2ee389 +cube4.rank5.repaired_partition=1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +cube4.rank5.repaired_block_weights=32,32 +cube4.rank5.repaired_weighted_cut=22 +cube10.rank2.repaired_upstream_partition_sha256=9a362041172c39f4b17d68b6919dd43e73260d7aa4bfec633f29924e81396817 +cube10.rank2.repaired_partition_file_sha256=353c210e5ccdb593144bbd18a4c7ae50682c42566f78d42fbfc80eca557cd24f +cube10.rank2.repaired_partition_sha256=1b39d02b62cb0c40536cb6543f7b4c3b81414bde6d302e56da373ef00202e7fe +cube10.rank2.repaired_block_weights=238,255,253,254 +cube10.rank2.repaired_weighted_cut=253 +cube10.rank4.repaired_upstream_partition_sha256=0b200061d7cabead1743692b4055fd1beb2ef833239ded2342020b844df872c1 +cube10.rank4.repaired_partition_file_sha256=9d7f5b5994621d1f9a1509734eb2d709d3219db21ded3f639dc87b820cb509e1 +cube10.rank4.repaired_partition_sha256=27a01faf2a3e79285cd8fa75746432c017f22ab7fe06d9e11fbe4eb2ef944cab +cube10.rank4.repaired_block_weights=235,252,257,256 +cube10.rank4.repaired_weighted_cut=264 diff --git a/parallel/parallel_src/tests/fixtures/cube_partition_oracle_validation_test.cmake b/parallel/parallel_src/tests/fixtures/cube_partition_oracle_validation_test.cmake new file mode 100644 index 00000000..d11994f9 --- /dev/null +++ b/parallel/parallel_src/tests/fixtures/cube_partition_oracle_validation_test.cmake @@ -0,0 +1,175 @@ +cmake_minimum_required(VERSION 4.0...4.3) + +foreach( + required_variable + IN ITEMS + TEST_CASE + ORACLE_SCRIPT + MANIFEST + GENERATOR + PARHIP + VERIFIER + MPIEXEC_EXECUTABLE + MPIEXEC_NUMPROC_FLAG + WORK_DIRECTORY +) + if(NOT DEFINED ${required_variable} OR "${${required_variable}}" STREQUAL "") + message(FATAL_ERROR "${required_variable} is required") + endif() +endforeach() + +foreach(required_file IN ITEMS ORACLE_SCRIPT MANIFEST GENERATOR PARHIP VERIFIER) + if(NOT EXISTS "${${required_file}}") + message(FATAL_ERROR + "${required_file} does not exist: ${${required_file}}" + ) + endif() +endforeach() + +file(READ "${MANIFEST}" valid_manifest) +set(mutated_manifest "${valid_manifest}") + +if(TEST_CASE STREQUAL "malformed-provenance") + set(search "upstream_compiler=GNU-16.2.1") + set(replacement "upstream_compiler") + set(expected_diagnostic + "malformed cube oracle manifest line 'upstream_compiler'" + ) +elseif(TEST_CASE STREQUAL "duplicate-provenance") + string(APPEND mutated_manifest + "\nupstream_mpi=Open-MPI-5.0.10-MPI-3.1\n" + ) + set(expected_diagnostic "duplicate cube oracle key 'upstream_mpi'") +elseif(TEST_CASE STREQUAL "missing-provenance") + set(search "cell_id=x+nx*(y+ny*z)\n") + set(replacement "") + set(expected_diagnostic "cube oracle manifest is missing 'cell_id'") +elseif(TEST_CASE STREQUAL "unknown-provenance") + string(APPEND mutated_manifest "\nupstream_build_host=unrecorded\n") + set(expected_diagnostic + "unknown cube oracle manifest key 'upstream_build_host'" + ) +elseif(TEST_CASE STREQUAL "invalid-compiler") + set(search "upstream_compiler=GNU-16.2.1") + set(replacement "upstream_compiler=GNU") + set(expected_diagnostic "invalid upstream compiler provenance 'GNU'") +elseif(TEST_CASE STREQUAL "invalid-mpi") + set(search "upstream_mpi=Open-MPI-5.0.10-MPI-3.1") + set(replacement "upstream_mpi=Open-MPI") + set(expected_diagnostic "invalid upstream MPI provenance 'Open-MPI'") +elseif(TEST_CASE STREQUAL "invalid-cell-id") + set(search "cell_id=x+nx*(y+ny*z)") + set(replacement "cell_id=z+nz*(y+ny*x)") + set(expected_diagnostic "unsupported cube cell-id recipe") +elseif(TEST_CASE STREQUAL "invalid-adjacency") + set(search "adjacency=sorted-six-face-neighborhood") + set(replacement "adjacency=unsorted-six-face-neighborhood") + set(expected_diagnostic "unsupported cube adjacency recipe") +elseif(TEST_CASE STREQUAL "invalid-preconfiguration") + set(search "cube4.preconfiguration=fastmesh") + set(replacement "cube4.preconfiguration=eco") + set(expected_diagnostic + "cube4.preconfiguration is 'eco', expected 'fastmesh'" + ) +elseif(TEST_CASE STREQUAL "invalid-seed") + set(search "cube4.seed=1") + set(replacement "cube4.seed=2") + set(expected_diagnostic "cube4.seed is 2, expected 1") +elseif(TEST_CASE STREQUAL "invalid-imbalance") + set(search "cube4.imbalance_percent=3") + set(replacement "cube4.imbalance_percent=4") + set(expected_diagnostic "cube4.imbalance_percent is 4, expected 3") +elseif(TEST_CASE STREQUAL "exact-cut") + set(search "cube4.rank1.weighted_cut=28") + set(replacement "cube4.rank1.weighted_cut=2") + set(expected_diagnostic "weighted cut is 28, expected 2") +elseif(TEST_CASE STREQUAL "orphaned-repair-provenance") + string(REGEX REPLACE + "[^\n]*\\.repaired_[^\n]*\n?" "" + mutated_manifest "${valid_manifest}" + ) + set(expected_diagnostic + "cube oracle repair provenance has no repair overlay" + ) +elseif(TEST_CASE STREQUAL "missing-repair-provenance") + set(search "repair_mpi=MPICH-5.0.1-MPI-5.0\n") + set(replacement "") + set(expected_diagnostic + "cube oracle repair provenance is missing 'repair_mpi'" + ) + set(test_ranks 5) +elseif(TEST_CASE STREQUAL "invalid-repair-provenance") + set(search "repair_compiler=GNU-16.2.1") + set(replacement "repair_compiler=GNU") + set(expected_diagnostic "invalid repair compiler provenance 'GNU'") + set(test_ranks 5) +elseif(TEST_CASE STREQUAL "mismatched-repair-upstream-anchor") + set(search + "cube4.rank5.repaired_upstream_partition_sha256=05d39781dac8fc7e376085023430956bd3b63e14b97ce44b59334aee059cc85a" + ) + set(replacement + "cube4.rank5.repaired_upstream_partition_sha256=0000000000000000000000000000000000000000000000000000000000000000" + ) + set(expected_diagnostic + "cube4.rank5 repaired upstream partition SHA-256 does not match" + ) + set(test_ranks 5) +else() + message(FATAL_ERROR "unknown cube oracle validation case '${TEST_CASE}'") +endif() + +if(NOT DEFINED test_ranks) + set(test_ranks 1) +endif() + +if(DEFINED search) + string(FIND "${valid_manifest}" "${search}" search_index) + if(search_index EQUAL -1) + message(FATAL_ERROR + "validation fixture no longer contains '${search}'" + ) + endif() + string(REPLACE "${search}" "${replacement}" + mutated_manifest "${valid_manifest}" + ) +endif() + +file(MAKE_DIRECTORY "${WORK_DIRECTORY}") +set(mutated_manifest_path "${WORK_DIRECTORY}/${TEST_CASE}.txt") +file(WRITE "${mutated_manifest_path}" "${mutated_manifest}") + +execute_process( + COMMAND + "${CMAKE_COMMAND}" + "-DGENERATOR=${GENERATOR}" + "-DPARHIP=${PARHIP}" + "-DVERIFIER=${VERIFIER}" + "-DMPIEXEC_EXECUTABLE=${MPIEXEC_EXECUTABLE}" + "-DMPIEXEC_NUMPROC_FLAG=${MPIEXEC_NUMPROC_FLAG}" + "-DMPIEXEC_PREFLAGS=${MPIEXEC_PREFLAGS}" + "-DMPIEXEC_POSTFLAGS=${MPIEXEC_POSTFLAGS}" + "-DMANIFEST=${mutated_manifest_path}" + "-DWORK_DIRECTORY=${WORK_DIRECTORY}/${TEST_CASE}-run" + -DFIXTURE=cube4 + -DNX=4 + -DNY=4 + -DNZ=4 + -DBLOCKS=2 + "-DRANKS=${test_ranks}" + -P "${ORACLE_SCRIPT}" + RESULT_VARIABLE oracle_result + OUTPUT_VARIABLE oracle_output + ERROR_VARIABLE oracle_error +) +set(oracle_log "${oracle_output}${oracle_error}") +if(oracle_result EQUAL 0) + message(FATAL_ERROR + "cube oracle accepted invalid '${TEST_CASE}' manifest\n${oracle_log}" + ) +endif() +string(FIND "${oracle_log}" "${expected_diagnostic}" diagnostic_index) +if(diagnostic_index EQUAL -1) + message(FATAL_ERROR + "cube oracle rejected '${TEST_CASE}' for the wrong reason; expected '${expected_diagnostic}'\n${oracle_log}" + ) +endif() diff --git a/parallel/parallel_src/tests/fixtures/cube_partition_test.cpp b/parallel/parallel_src/tests/fixtures/cube_partition_test.cpp new file mode 100644 index 00000000..567e462d --- /dev/null +++ b/parallel/parallel_src/tests/fixtures/cube_partition_test.cpp @@ -0,0 +1,70 @@ +#include +#include +#include +#include + +#include + +#include "fixtures/cube_partition.h" + +namespace { +using parhip::testing::cube_dimensions; +using parhip::testing::cube_graph; +using parhip::testing::evaluate_cube_partition; +using parhip::testing::read_text_partition; +} // namespace + +TEST_CASE("cube partition verifier recomputes block weights and cut", + "[cube][partition][verifier]") { + auto const graph = cube_graph{cube_dimensions{.nx = 2, .ny = 2, .nz = 1}}; + auto const partition = std::vector{0, 0, 1, 1}; + + auto const metrics = evaluate_cube_partition(graph, partition, 2, 0); + + REQUIRE(metrics.block_weights == std::vector{2, 2}); + REQUIRE(metrics.maximum_block_weight == 2); + REQUIRE(metrics.weighted_cut == 2); +} + +TEST_CASE("cube partition verifier applies the exact percent balance bound", + "[cube][partition][verifier]") { + auto const graph = cube_graph{cube_dimensions{.nx = 2, .ny = 2, .nz = 1}}; + auto const partition = std::vector{0, 0, 0, 1}; + + REQUIRE_THROWS_AS(evaluate_cube_partition(graph, partition, 2, 0), + std::runtime_error); + auto const metrics = evaluate_cube_partition(graph, partition, 2, 50); + REQUIRE(metrics.maximum_block_weight == 3); + REQUIRE(metrics.block_weights == std::vector{3, 1}); +} + +TEST_CASE("cube partition verifier rejects missing, extra, and invalid blocks", + "[cube][partition][verifier]") { + auto const graph = cube_graph{cube_dimensions{.nx = 2, .ny = 2, .nz = 1}}; + + REQUIRE_THROWS_AS( + evaluate_cube_partition(graph, std::vector{0, 1, 0}, 2, 0), + std::invalid_argument); + REQUIRE_THROWS_AS(evaluate_cube_partition( + graph, std::vector{0, 1, 0, 1, 0}, 2, 0), + std::invalid_argument); + REQUIRE_THROWS_AS(evaluate_cube_partition( + graph, std::vector{0, 1, 2, 0}, 2, 50), + std::runtime_error); +} + +TEST_CASE("text partition reader requires exactly one block per vertex", + "[cube][partition][verifier]") { + auto valid = std::istringstream{"0\n1\n 0 \n1\n"}; + REQUIRE(read_text_partition(valid, 4) == + std::vector{0, 1, 0, 1}); + + auto missing = std::istringstream{"0\n1\n0\n"}; + REQUIRE_THROWS_AS(read_text_partition(missing, 4), std::runtime_error); + + auto extra = std::istringstream{"0\n1\n0\n1\n0\n"}; + REQUIRE_THROWS_AS(read_text_partition(extra, 4), std::runtime_error); + + auto malformed = std::istringstream{"0\n1\nnot-a-block\n1\n"}; + REQUIRE_THROWS_AS(read_text_partition(malformed, 4), std::runtime_error); +} diff --git a/parallel/parallel_src/tests/fixtures/cube_partition_verify.cpp b/parallel/parallel_src/tests/fixtures/cube_partition_verify.cpp new file mode 100644 index 00000000..83cacf8e --- /dev/null +++ b/parallel/parallel_src/tests/fixtures/cube_partition_verify.cpp @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fixtures/cube_partition.h" + +namespace { +template +void write_joined(std::ostream& output, + Range const& values, + std::string_view separator) { + auto first = true; + for (auto const& value : values) { + if (!first) { + output << separator; + } + output << value; + first = false; + } +} + +[[nodiscard]] auto parse_unsigned(std::string_view text, + std::uint64_t& value) noexcept -> bool { + auto const [end, error] = + std::from_chars(text.data(), text.data() + text.size(), value); + return error == std::errc{} && end == text.data() + text.size(); +} +} // namespace + +int main(int argc, char* argv[]) { + if (argc != 7) { + std::cerr << "usage: " + << (argc > 0 ? argv[0] : "kahip_cube_partition_verify") + << " NX NY NZ BLOCKS IMBALANCE_PERCENT PARTITION.txtp\n"; + return 64; + } + + auto dimensions = parhip::testing::cube_dimensions{}; + auto block_count = std::uint64_t{}; + auto imbalance_percent = std::uint64_t{}; + if (!parse_unsigned(argv[1], dimensions.nx) || + !parse_unsigned(argv[2], dimensions.ny) || + !parse_unsigned(argv[3], dimensions.nz) || + !parse_unsigned(argv[4], block_count) || + !parse_unsigned(argv[5], imbalance_percent)) { + std::cerr << "cube verification arguments must be unsigned integers\n"; + return 64; + } + + try { + auto const graph = parhip::testing::cube_graph{dimensions}; + auto input = std::ifstream{argv[6]}; + if (!input) { + std::cerr << "cannot open partition '" << argv[6] << "'\n"; + return 1; + } + auto const partition = + parhip::testing::read_text_partition(input, graph.vertex_count()); + auto const metrics = parhip::testing::evaluate_cube_partition( + graph, partition, block_count, imbalance_percent); + std::cout << "verified vertices=" << graph.vertex_count() + << " blocks=" << block_count + << " maximum-block-weight=" << metrics.maximum_block_weight + << " block-weights=["; + write_joined(std::cout, metrics.block_weights, ","); + std::cout << "] weighted-cut=" << metrics.weighted_cut << '\n'; + } catch (std::exception const& error) { + std::cerr << "partition verification failed: " << error.what() << '\n'; + return 1; + } +} diff --git a/parallel/parallel_src/tests/fixtures/mpi_trace_oracle/.gitattributes b/parallel/parallel_src/tests/fixtures/mpi_trace_oracle/.gitattributes new file mode 100644 index 00000000..7035190e --- /dev/null +++ b/parallel/parallel_src/tests/fixtures/mpi_trace_oracle/.gitattributes @@ -0,0 +1 @@ +task-5-upstream-trace.patch -text -diff diff --git a/parallel/parallel_src/tests/fixtures/mpi_trace_oracle/task-5-oracle-golden.txt b/parallel/parallel_src/tests/fixtures/mpi_trace_oracle/task-5-oracle-golden.txt new file mode 100644 index 00000000..cf8f50dc --- /dev/null +++ b/parallel/parallel_src/tests/fixtures/mpi_trace_oracle/task-5-oracle-golden.txt @@ -0,0 +1,35 @@ +KaHIP Task 5 oracle provenance +upstream_revision=5935f349f65f1788a9b68fcf6d853e698d86956d +instrumentation_patch=task-5-upstream-trace.patch +instrumentation_patch_sha256=f8542af7d02588b1491b39c30f99ba4c30b5a98b2fe04e0bfaab781a27020180 + +tuple.graph=examples/rgg_n_2_15_s0.graph +tuple.ranks=2 +tuple.k=2 +tuple.preconfiguration=ultrafastmesh +tuple.seed=0 + +partition_sha256=a600acd0029ee9342e4f7c5b041d224a308b874c85fd35bdbcd3a5a73d48cdd0 +trace_format=kahip-mpi-trace-v3 +canonical_rank_aggregate_records=436721 +canonical_rank_aggregate_sha256=a179bb30213dbb26638657a1d611e951a8bf900b817647fc03d4c700a83f0a18 +upstream_rank0_sha256=91cb490db1c03a97748b7a1a392cec42a914f485ea339f6b098f54d849132f13 +upstream_rank1_sha256=89cc3a96d61e55a8575d0107f9e34a417abf6d3b99c2d60c5ec99f7fb40f59d5 +candidate_rank0_sha256=858fbed2438c80ee55e486a989ce15a643a60769a4b33e78cbf4bf423bd64a2d +candidate_rank1_sha256=37eb5483b575963e49236b20e6301b3f8aa7aef7f2e20a448bcb66f38d8aa91a + +stage.graph-distribution-node=32768 +stage.graph-distribution-edge=320480 +stage.contraction-label=32768 +stage.quotient-node-weight=2898 +stage.quotient-edge=13602 +stage.projection-request=62 +stage.projection-reply=62 +stage.ghost-update=1313 +stage.final-partition=32768 + +# The configured parhip executable excludes --num_vcycles under +# PARALLEL_LABEL_COMPRESSION, so this fixed tuple does not execute the +# vcycle-only block-propagation path. Its exact record schema and hook source +# are covered by the focused trace test; algorithmic block-path parity remains +# outside this Task 5 tuple. diff --git a/parallel/parallel_src/tests/fixtures/mpi_trace_oracle/task-5-upstream-trace.patch b/parallel/parallel_src/tests/fixtures/mpi_trace_oracle/task-5-upstream-trace.patch new file mode 100644 index 00000000..04460ff5 --- /dev/null +++ b/parallel/parallel_src/tests/fixtures/mpi_trace_oracle/task-5-upstream-trace.patch @@ -0,0 +1,862 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index b580d0a..0861d1c 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -118,6 +118,8 @@ option(NOMPI "disable all targets that depend on MPI (kaffpaE, ParHIP)" OFF) + # ParHIP + option(PARHIP "build ParHIP" ON) + option(DETERMINISTIC_PARHIP "enforce deterministic computations in ParHIP" OFF) ++option(KAHIP_ENABLE_TASK5_ORACLE_TRACE ++ "enable Task 5 trace-only upstream oracle instrumentation" OFF) + + # Look for MPI (needed for ParHIP and kaffpaE) + # Report which MPI we actually found, +diff --git a/parallel/parallel_src/CMakeLists.txt b/parallel/parallel_src/CMakeLists.txt +index ae973e7..cdb9a36 100644 +--- a/parallel/parallel_src/CMakeLists.txt ++++ b/parallel/parallel_src/CMakeLists.txt +@@ -37,6 +37,9 @@ set(LIBPARALLEL_SOURCE_FILES + extern/argtable3-3.2.2/argtable3.c) + add_library(libparallel OBJECT ${LIBPARALLEL_SOURCE_FILES}) + target_include_directories(libparallel PUBLIC $) ++target_compile_definitions( ++ libparallel PRIVATE ++ KAHIP_ENABLE_TASK5_ORACLE_TRACE=$) + + set(LIBGRAPH2BGF_SOURCE_FILES + lib/data_structure/parallel_graph_access.cpp +@@ -62,6 +65,9 @@ add_library(libdspac OBJECT ${LIBDSPAC_SOURCE_FILES}) + + add_executable(parhip app/parhip.cpp $) + target_compile_definitions(parhip PRIVATE "-DGRAPH_GENERATOR_MPI" "-DGRAPHGEN_DISTRIBUTED_MEMORY" "-DPARALLEL_LABEL_COMPRESSION") ++target_compile_definitions( ++ parhip PRIVATE ++ KAHIP_ENABLE_TASK5_ORACLE_TRACE=$) + target_link_libraries(parhip PRIVATE libmodified_kahip_interface) + install(TARGETS parhip DESTINATION bin) + +diff --git a/parallel/parallel_src/app/parhip.cpp b/parallel/parallel_src/app/parhip.cpp +index 124ef0b..3e71f42 100644 +--- a/parallel/parallel_src/app/parhip.cpp ++++ b/parallel/parallel_src/app/parhip.cpp +@@ -18,6 +18,7 @@ + #include + + #include "communication/mpi_tools.h" ++#include "communication/task5_oracle_trace.h" + #include "communication/dummy_operations.h" + #include "data_structure/parallel_graph_access.h" + #include "distributed_partitioning/distributed_partitioner.h" +@@ -80,6 +81,26 @@ int main(int argn, char **argv) { + + parallel_graph_access G(communicator); + parallel_graph_io::readGraphWeighted(partition_config, G, graph_filename, rank, size, communicator); ++#if KAHIP_ENABLE_TASK5_ORACLE_TRACE ++ KAHIP_TASK5_ORACLE_SET_HIERARCHY( ++ 0, 0, kahip_task5_oracle_trace::input_epoch); ++ forall_local_nodes(G, node) { ++ KAHIP_TASK5_ORACLE_TRACE( ++ kahip_task5_oracle_trace::graph_distribution_node( ++ kahip_task5_oracle_trace::current_hierarchy(), ++ G.getGlobalID(node), rank, ++ G.getNodeWeight(node))); ++ forall_out_edges(G, e, node) { ++ NodeID target = G.getEdgeTarget(e); ++ KAHIP_TASK5_ORACLE_TRACE( ++ kahip_task5_oracle_trace::graph_distribution_edge( ++ kahip_task5_oracle_trace::current_hierarchy(), ++ G.getGlobalID(node), rank, ++ G.getGlobalID(target), ++ G.getEdgeWeight(e))); ++ } endfor ++ } endfor ++#endif + //parallel_graph_io::readGraphWeightedFlexible(G, graph_filename, rank, size, communicator); + if( rank == ROOT ) std::cout << "took " << t.elapsed() << std::endl; + if( rank == ROOT ) std::cout << "n:" << G.number_of_global_nodes() << " m: " << G.number_of_global_edges() << std::endl; +@@ -143,6 +164,21 @@ int main(int argn, char **argv) { + dpart.perform_partitioning( communicator, partition_config, G); + + MPI_Barrier(communicator); ++#if KAHIP_ENABLE_TASK5_ORACLE_TRACE ++ KAHIP_TASK5_ORACLE_SET_HIERARCHY( ++ partition_config.num_vcycles == 0 ++ ? 0 ++ : partition_config.num_vcycles - 1, ++ 0, kahip_task5_oracle_trace::final_partition_epoch); ++ forall_local_nodes(G, node) { ++ KAHIP_TASK5_ORACLE_TRACE( ++ kahip_task5_oracle_trace::final_partition( ++ kahip_task5_oracle_trace::current_hierarchy(), ++ G.getGlobalID(node), rank, ++ G.getNodeLabel(node))); ++ } endfor ++ KAHIP_TASK5_ORACLE_WRITE(communicator); ++#endif + + double running_time = t.elapsed(); + distributed_quality_metrics qm; +diff --git a/parallel/parallel_src/lib/communication/task5_oracle_trace.h b/parallel/parallel_src/lib/communication/task5_oracle_trace.h +new file mode 100644 +index 0000000..8eb2b4e +--- /dev/null ++++ b/parallel/parallel_src/lib/communication/task5_oracle_trace.h +@@ -0,0 +1,492 @@ ++#ifndef KAHIP_TASK5_ORACLE_TRACE_H ++#define KAHIP_TASK5_ORACLE_TRACE_H ++ ++#ifndef KAHIP_ENABLE_TASK5_ORACLE_TRACE ++#define KAHIP_ENABLE_TASK5_ORACLE_TRACE 0 ++#endif ++ ++#if KAHIP_ENABLE_TASK5_ORACLE_TRACE ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++namespace kahip_task5_oracle_trace { ++enum stage { ++ graph_distribution_node_stage, ++ graph_distribution_edge_stage, ++ contraction_label_stage, ++ quotient_node_weight_stage, ++ quotient_edge_stage, ++ projection_request_stage, ++ projection_reply_stage, ++ ghost_update_stage, ++ block_propagation_stage, ++ final_partition_stage ++}; ++ ++enum epoch { ++ input_epoch, ++ coarsening_epoch, ++ contraction_epoch, ++ initial_partition_epoch, ++ projection_epoch, ++ refinement_epoch, ++ final_partition_epoch ++}; ++ ++struct hierarchy_position { ++ unsigned cycle; ++ unsigned level; ++ epoch epoch_id; ++ unsigned iteration; ++ unsigned round; ++}; ++ ++struct semantic_actors { ++ int owner; ++ int requester; ++ int receiver; ++}; ++ ++struct record { ++ stage stage_id; ++ hierarchy_position hierarchy; ++ unsigned long long global_id; ++ semantic_actors actors; ++ std::string semantic_key; ++ std::string payload; ++}; ++ ++inline const char* stage_name(stage value) { ++ static const char* names[] = { ++ "graph-distribution-node", "graph-distribution-edge", ++ "contraction-label", "quotient-node-weight", "quotient-edge", ++ "projection-request", "projection-reply", "ghost-update", ++ "block-propagation", "final-partition"}; ++ return names[static_cast(value)]; ++} ++ ++inline const char* epoch_name(epoch value) { ++ static const char* names[] = { ++ "input", "coarsening", "contraction", "initial-partition", ++ "projection", "refinement", "final-partition"}; ++ return names[static_cast(value)]; ++} ++ ++inline std::string rank_name(int rank) { ++ return rank < 0 ? "-" : std::to_string(rank); ++} ++ ++inline hierarchy_position& current_hierarchy_storage() { ++ static hierarchy_position value = {0, 0, input_epoch, 0, 0}; ++ return value; ++} ++ ++inline hierarchy_position current_hierarchy() { ++ return current_hierarchy_storage(); ++} ++ ++inline hierarchy_position current_hierarchy_with_round(unsigned round) { ++ hierarchy_position value = current_hierarchy(); ++ value.round = round; ++ return value; ++} ++ ++inline void set_hierarchy(unsigned cycle, unsigned level, epoch epoch_id) { ++ hierarchy_position value = {cycle, level, epoch_id, 0, 0}; ++ current_hierarchy_storage() = value; ++} ++ ++inline void set_iteration(unsigned iteration) { ++ current_hierarchy_storage().iteration = iteration; ++} ++ ++inline record graph_distribution_node(hierarchy_position hierarchy, ++ unsigned long long id, int owner, ++ unsigned long long weight) { ++ semantic_actors actors = {owner, -1, owner}; ++ return {graph_distribution_node_stage, hierarchy, id, actors, ++ "owner:" + std::to_string(owner), ++ "weight=" + std::to_string(weight)}; ++} ++ ++inline record graph_distribution_edge(hierarchy_position hierarchy, ++ unsigned long long source, int owner, ++ unsigned long long target, ++ unsigned long long weight) { ++ semantic_actors actors = {owner, -1, owner}; ++ return {graph_distribution_edge_stage, hierarchy, source, actors, ++ "target:" + std::to_string(target), ++ "weight=" + std::to_string(weight)}; ++} ++ ++inline record contraction_label(hierarchy_position hierarchy, ++ unsigned long long id, int owner, ++ unsigned long long label, ++ unsigned long long coarse) { ++ semantic_actors actors = {owner, -1, owner}; ++ return {contraction_label_stage, hierarchy, id, actors, ++ "label:" + std::to_string(label), ++ "coarse=" + std::to_string(coarse)}; ++} ++ ++inline record quotient_node_weight(hierarchy_position hierarchy, ++ unsigned long long id, int owner, ++ unsigned long long weight) { ++ semantic_actors actors = {owner, -1, owner}; ++ return {quotient_node_weight_stage, hierarchy, id, actors, "node", ++ "weight=" + std::to_string(weight)}; ++} ++ ++inline record quotient_edge(hierarchy_position hierarchy, ++ unsigned long long source, int owner, ++ unsigned long long target, ++ unsigned long long weight) { ++ semantic_actors actors = {owner, -1, owner}; ++ return {quotient_edge_stage, hierarchy, source, actors, ++ "target:" + std::to_string(target), ++ "weight=" + std::to_string(weight)}; ++} ++ ++inline record projection_request(hierarchy_position hierarchy, ++ unsigned long long request_id, int requester, ++ int owner, unsigned long long coarse) { ++ semantic_actors actors = {owner, requester, owner}; ++ return {projection_request_stage, hierarchy, coarse, actors, ++ "request:" + std::to_string(request_id), ++ "requester=" + std::to_string(requester) + ++ " owner=" + std::to_string(owner)}; ++} ++ ++inline record projection_reply(hierarchy_position hierarchy, ++ unsigned long long request_id, int requester, ++ int owner, unsigned long long coarse, ++ unsigned long long label) { ++ semantic_actors actors = {owner, requester, requester}; ++ return {projection_reply_stage, hierarchy, coarse, actors, ++ "request:" + std::to_string(request_id), ++ "requester=" + std::to_string(requester) + ++ " owner=" + std::to_string(owner) + ++ " label=" + std::to_string(label)}; ++} ++ ++inline record ghost_update(hierarchy_position hierarchy, ++ unsigned long long id, int owner, int receiver, ++ unsigned long long label) { ++ semantic_actors actors = {owner, -1, receiver}; ++ return {ghost_update_stage, hierarchy, id, actors, "label", ++ "label=" + std::to_string(label)}; ++} ++ ++inline record block_propagation(hierarchy_position hierarchy, ++ unsigned long long id, int owner, int receiver, ++ unsigned long long block) { ++ semantic_actors actors = {owner, -1, receiver}; ++ return {block_propagation_stage, hierarchy, id, actors, "block", ++ "block=" + std::to_string(block)}; ++} ++ ++inline record final_partition(hierarchy_position hierarchy, ++ unsigned long long id, int owner, ++ unsigned long long block) { ++ semantic_actors actors = {owner, -1, owner}; ++ return {final_partition_stage, hierarchy, id, actors, "partition", ++ "block=" + std::to_string(block)}; ++} ++ ++inline std::vector& records() { ++ static std::vector values; ++ return values; ++} ++ ++inline void append(record value) { ++ records().push_back(value); ++} ++ ++inline std::string canonical_text() { ++ std::vector values = records(); ++ std::sort(values.begin(), values.end(), ++ [](const record& lhs, const record& rhs) { ++ return std::tie(lhs.stage_id, lhs.hierarchy.cycle, ++ lhs.hierarchy.level, lhs.hierarchy.epoch_id, ++ lhs.hierarchy.iteration, lhs.hierarchy.round, ++ lhs.global_id, ++ lhs.actors.owner, lhs.actors.requester, ++ lhs.actors.receiver, lhs.semantic_key, ++ lhs.payload) < ++ std::tie(rhs.stage_id, rhs.hierarchy.cycle, ++ rhs.hierarchy.level, rhs.hierarchy.epoch_id, ++ rhs.hierarchy.iteration, rhs.hierarchy.round, ++ rhs.global_id, ++ rhs.actors.owner, rhs.actors.requester, ++ rhs.actors.receiver, rhs.semantic_key, ++ rhs.payload); ++ }); ++ std::string output = ++ "kahip-mpi-trace-v3 upstream=" ++ "5935f349f65f1788a9b68fcf6d853e698d86956d\n"; ++ for (std::vector::const_iterator value = values.begin(); ++ value != values.end(); ++value) { ++ output += std::string(stage_name(value->stage_id)) + ++ " cycle=" + std::to_string(value->hierarchy.cycle) + ++ " level=" + std::to_string(value->hierarchy.level) + ++ " epoch=" + std::string(epoch_name(value->hierarchy.epoch_id)) + ++ " iteration=" + std::to_string(value->hierarchy.iteration) + ++ " round=" + std::to_string(value->hierarchy.round) + ++ " global=" + std::to_string(value->global_id) + ++ " owner=" + rank_name(value->actors.owner) + ++ " requester=" + rank_name(value->actors.requester) + ++ " receiver=" + rank_name(value->actors.receiver) + ++ " key=" + value->semantic_key; ++ if (!value->payload.empty()) output += " " + value->payload; ++ output += "\n"; ++ } ++ return output; ++} ++ ++inline std::string sanitize_run_id(const std::string& run_id) { ++ std::string result = run_id; ++ for (std::string::iterator character = result.begin(); ++ character != result.end(); ++character) { ++ const bool ascii_alphanumeric = ++ (*character >= 'a' && *character <= 'z') || ++ (*character >= 'A' && *character <= 'Z') || ++ (*character >= '0' && *character <= '9'); ++ if (!ascii_alphanumeric && *character != '-' && *character != '_') { ++ *character = '_'; ++ } ++ } ++ return result; ++} ++ ++inline std::uint64_t stable_run_id_hash(const std::string& run_id) { ++ const std::uint64_t offset_basis = 14695981039346656037ULL; ++ const std::uint64_t prime = 1099511628211ULL; ++ std::uint64_t hash = offset_basis; ++ for (std::string::const_iterator character = run_id.begin(); ++ character != run_id.end(); ++character) { ++ hash ^= static_cast(*character); ++ hash *= prime; ++ } ++ return hash; ++} ++ ++inline std::string hexadecimal(std::uint64_t value) { ++ static const char digits[] = "0123456789abcdef"; ++ std::string result(16, '0'); ++ for (std::size_t index = result.size(); index > 0; --index) { ++ const unsigned digit = static_cast(value & 0xfU); ++ result[index - 1] = digits[digit]; ++ value >>= 4U; ++ } ++ return result; ++} ++ ++inline std::string run_id_filename_component(const std::string& run_id) { ++ return sanitize_run_id(run_id) + "-" + ++ hexadecimal(stable_run_id_hash(run_id)); ++} ++ ++inline std::string requested_run_id() { ++ static const char* variables[] = { ++ "KAHIP_MPI_TRACE_RUN_ID", "SLURM_JOB_ID", "PBS_JOBID", "LSB_JOBID", ++ "PMI_JOBID", "OMPI_MCA_orte_ess_jobid"}; ++ for (unsigned index = 0; index < sizeof(variables) / sizeof(variables[0]); ++ ++index) { ++ const char* value = std::getenv(variables[index]); ++ if (value != NULL && *value != '\0') return value; ++ } ++ return ""; ++} ++ ++inline std::string rank_file_path(const std::string& base, ++ const std::string& run_id, int rank) { ++ if (run_id.empty()) { ++ throw std::invalid_argument( ++ "oracle trace filename requires a collectively resolved run ID"); ++ } ++ return base + ".run-" + run_id_filename_component(run_id) + ".rank" + ++ std::to_string(rank) + ".trace"; ++} ++ ++inline std::string automatic_run_id() { ++ std::random_device entropy; ++ std::uint64_t first = (static_cast(entropy()) << 32U) | ++ static_cast(entropy()); ++ std::uint64_t second = (static_cast(entropy()) << 32U) | ++ static_cast(entropy()); ++ first ^= static_cast( ++ std::chrono::system_clock::now().time_since_epoch().count()); ++ second ^= static_cast( ++ std::chrono::steady_clock::now().time_since_epoch().count()); ++ return "auto-" + hexadecimal(first) + hexadecimal(second); ++} ++ ++class owned_communicator { ++ public: ++ explicit owned_communicator(MPI_Comm source) : value_(MPI_COMM_NULL) { ++ if (MPI_Comm_dup(source, &value_) != MPI_SUCCESS) { ++ throw std::runtime_error("oracle trace communicator duplication failed"); ++ } ++ const int result = MPI_Comm_set_errhandler(value_, MPI_ERRORS_RETURN); ++ if (result != MPI_SUCCESS) { ++ MPI_Comm_free(&value_); ++ throw std::runtime_error("oracle trace error-handler setup failed"); ++ } ++ } ++ ++ ~owned_communicator() { ++ if (value_ != MPI_COMM_NULL) MPI_Comm_free(&value_); ++ } ++ ++ MPI_Comm get() const { return value_; } ++ ++ private: ++ owned_communicator(const owned_communicator&); ++ owned_communicator& operator=(const owned_communicator&); ++ ++ MPI_Comm value_; ++}; ++ ++inline std::string broadcast_string(MPI_Comm communicator, int rank, ++ const std::string& root_value) { ++ unsigned long long length = ++ rank == 0 ? static_cast(root_value.size()) : 0ULL; ++ if (MPI_Bcast(&length, 1, MPI_UNSIGNED_LONG_LONG, 0, communicator) != ++ MPI_SUCCESS) { ++ throw std::runtime_error("oracle trace run-ID length broadcast failed"); ++ } ++ if (length > ++ static_cast(std::numeric_limits::max())) { ++ throw std::runtime_error("oracle trace run ID is too long"); ++ } ++ std::vector bytes(static_cast(length)); ++ if (rank == 0) std::copy(root_value.begin(), root_value.end(), bytes.begin()); ++ char* data = bytes.empty() ? NULL : &bytes[0]; ++ if (MPI_Bcast(data, static_cast(length), MPI_CHAR, 0, communicator) != ++ MPI_SUCCESS) { ++ throw std::runtime_error("oracle trace run-ID payload broadcast failed"); ++ } ++ return std::string(bytes.begin(), bytes.end()); ++} ++ ++inline bool resolve_base_path_collectively(MPI_Comm communicator, ++ const char* local_base_path, ++ std::string& base_path) { ++ int rank = 0; ++ if (MPI_Comm_rank(communicator, &rank) != MPI_SUCCESS) { ++ throw std::runtime_error("oracle trace path rank query failed"); ++ } ++ const int local_present = local_base_path == NULL ? 0 : 1; ++ int root_present = rank == 0 ? local_present : 0; ++ if (MPI_Bcast(&root_present, 1, MPI_INT, 0, communicator) != MPI_SUCCESS) { ++ throw std::runtime_error("oracle trace path presence broadcast failed"); ++ } ++ const std::string root_path = broadcast_string( ++ communicator, rank, ++ rank == 0 && local_base_path != NULL ? local_base_path : ""); ++ const int mismatch = ++ local_present != root_present || ++ (local_present != 0 && local_base_path != root_path) ++ ? 1 ++ : 0; ++ int any_mismatch = 0; ++ if (MPI_Allreduce(&mismatch, &any_mismatch, 1, MPI_INT, MPI_MAX, ++ communicator) != MPI_SUCCESS) { ++ throw std::runtime_error("oracle trace path validation failed"); ++ } ++ if (any_mismatch != 0) { ++ throw std::runtime_error( ++ "MPI trace path differs across communicator ranks"); ++ } ++ if (root_present == 0) return false; ++ base_path = root_path; ++ return true; ++} ++ ++inline std::string resolve_run_id_collectively(MPI_Comm communicator, ++ const std::string& local_id) { ++ int rank = 0; ++ if (MPI_Comm_rank(communicator, &rank) != MPI_SUCCESS) { ++ throw std::runtime_error("oracle trace rank query failed"); ++ } ++ const std::string root_id = ++ broadcast_string(communicator, rank, rank == 0 ? local_id : ""); ++ const int mismatch = local_id != root_id ? 1 : 0; ++ int any_mismatch = 0; ++ if (MPI_Allreduce(&mismatch, &any_mismatch, 1, MPI_INT, MPI_MAX, ++ communicator) != MPI_SUCCESS) { ++ throw std::runtime_error("oracle trace run-ID validation failed"); ++ } ++ if (any_mismatch != 0) { ++ throw std::runtime_error( ++ "oracle trace run ID differs across communicator ranks"); ++ } ++ if (!root_id.empty()) return root_id; ++ return broadcast_string(communicator, rank, ++ rank == 0 ? automatic_run_id() : ""); ++} ++ ++inline void write_rank_file_if_requested(MPI_Comm communicator) { ++ owned_communicator owned(communicator); ++ const MPI_Comm collective_communicator = owned.get(); ++ std::string base; ++ if (!resolve_base_path_collectively( ++ collective_communicator, std::getenv("KAHIP_MPI_TRACE_PATH"), ++ base)) { ++ return; ++ } ++ int rank = 0; ++ if (MPI_Comm_rank(collective_communicator, &rank) != MPI_SUCCESS) { ++ throw std::runtime_error("oracle trace rank query failed"); ++ } ++ const std::string run_id = ++ resolve_run_id_collectively(collective_communicator, ++ requested_run_id()); ++ const std::string path = rank_file_path(base, run_id, rank); ++ std::ofstream output(path.c_str(), std::ios::binary | std::ios::trunc); ++ if (!output) throw std::runtime_error("oracle trace open failed"); ++ output << canonical_text(); ++ if (!output) throw std::runtime_error("oracle trace write failed"); ++} ++} // namespace kahip_task5_oracle_trace ++ ++#define KAHIP_TASK5_ORACLE_TRACE(expression) \ ++ do { kahip_task5_oracle_trace::append((expression)); } while (false) ++#define KAHIP_TASK5_ORACLE_SET_HIERARCHY(cycle, level, epoch_value) \ ++ do { \ ++ kahip_task5_oracle_trace::set_hierarchy( \ ++ static_cast(cycle), static_cast(level), \ ++ (epoch_value)); \ ++ } while (false) ++#define KAHIP_TASK5_ORACLE_SET_ITERATION(iteration_value) \ ++ do { \ ++ kahip_task5_oracle_trace::set_iteration( \ ++ static_cast(iteration_value)); \ ++ } while (false) ++#define KAHIP_TASK5_ORACLE_WRITE(communicator) \ ++ do { kahip_task5_oracle_trace::write_rank_file_if_requested(communicator); } \ ++ while (false) ++ ++#else ++ ++#define KAHIP_TASK5_ORACLE_TRACE(expression) do { } while (false) ++#define KAHIP_TASK5_ORACLE_SET_HIERARCHY(cycle, level, epoch_value) \ ++ do { } while (false) ++#define KAHIP_TASK5_ORACLE_SET_ITERATION(iteration_value) \ ++ do { } while (false) ++#define KAHIP_TASK5_ORACLE_WRITE(communicator) do { } while (false) ++ ++#endif ++#endif +diff --git a/parallel/parallel_src/lib/data_structure/parallel_graph_access.h b/parallel/parallel_src/lib/data_structure/parallel_graph_access.h +index 7e73ffa..ded5f78 100644 +--- a/parallel/parallel_src/lib/data_structure/parallel_graph_access.h ++++ b/parallel/parallel_src/lib/data_structure/parallel_graph_access.h +@@ -17,6 +17,7 @@ + #include + + #include "data_structure/balance_management.h" ++#include "communication/task5_oracle_trace.h" + #include "definitions.h" + #include "partition_config.h" + #include "tools/timer.h" +@@ -924,6 +925,13 @@ void ghost_node_communication::receive_messages_of_neighbors() { + NodeID local_id = m_G->m_global_to_local_id[global_id]; + m_G->update_non_contained_block_balance(m_G->getNodeLabel(local_id), label, m_G->getNodeWeight(local_id)); + m_G->setNodeLabel(local_id, label); ++ KAHIP_TASK5_ORACLE_TRACE( ++ kahip_task5_oracle_trace::ghost_update( ++ kahip_task5_oracle_trace:: ++ current_hierarchy_with_round( ++ static_cast( ++ m_recv_iteration)), ++ global_id, st.MPI_SOURCE, m_rank, label)); + } + } + +@@ -1033,6 +1041,10 @@ inline void ghost_node_communication::update_ghost_node_data_global() { + NodeID label = message[i+1]; + + m_G->setNodeLabel( m_G->m_global_to_local_id[global_id], label); ++ KAHIP_TASK5_ORACLE_TRACE( ++ kahip_task5_oracle_trace::ghost_update( ++ kahip_task5_oracle_trace::current_hierarchy(), ++ global_id, st.MPI_SOURCE, m_rank, label)); + } + } + +diff --git a/parallel/parallel_src/lib/distributed_partitioning/distributed_partitioner.cpp b/parallel/parallel_src/lib/distributed_partitioning/distributed_partitioner.cpp +index d487f73..baad526 100644 +--- a/parallel/parallel_src/lib/distributed_partitioning/distributed_partitioner.cpp ++++ b/parallel/parallel_src/lib/distributed_partitioning/distributed_partitioner.cpp +@@ -6,6 +6,7 @@ + *****************************************************************************/ + + #include ++#include "communication/task5_oracle_trace.h" + #include "communication/mpi_tools.h" + #include "distributed_partitioner.h" + #include "initial_partitioning/initial_partitioning.h" +@@ -149,6 +150,8 @@ void distributed_partitioner::vcycle( MPI_Comm communicator, PPartitionConfig & + + + m_level++; ++ KAHIP_TASK5_ORACLE_SET_HIERARCHY( ++ m_cycle, m_level, kahip_task5_oracle_trace::coarsening_epoch); + config.label_iterations = config.label_iterations_coarsening; + config.total_num_labels = G.number_of_global_nodes(); + // +@@ -173,6 +176,9 @@ void distributed_partitioner::vcycle( MPI_Comm communicator, PPartitionConfig & + t.restart(); + + { ++ KAHIP_TASK5_ORACLE_SET_HIERARCHY( ++ m_cycle, m_level, ++ kahip_task5_oracle_trace::contraction_epoch); + parallel_contraction parallel_contract; + parallel_contract.contract_to_distributed_quotient( communicator, config, G, Q); // contains one Barrier + +@@ -209,6 +215,9 @@ void distributed_partitioner::vcycle( MPI_Comm communicator, PPartitionConfig & + #endif + t.restart(); + ++ KAHIP_TASK5_ORACLE_SET_HIERARCHY( ++ m_cycle, m_level, ++ kahip_task5_oracle_trace::initial_partition_epoch); + initial_partitioning_algorithm ip; + ip.perform_partitioning( communicator, config, Q ); + +@@ -229,6 +238,8 @@ void distributed_partitioner::vcycle( MPI_Comm communicator, PPartitionConfig & + #endif + + t.restart(); ++ KAHIP_TASK5_ORACLE_SET_HIERARCHY( ++ m_cycle, m_level, kahip_task5_oracle_trace::projection_epoch); + parallel_projection parallel_project; + parallel_project.parallel_project( communicator, G, Q ); // contains a Barrier + +@@ -242,6 +253,9 @@ void distributed_partitioner::vcycle( MPI_Comm communicator, PPartitionConfig & + config.label_iterations = config.label_iterations_refinement; + + if( config.label_iterations != 0 ) { ++ KAHIP_TASK5_ORACLE_SET_HIERARCHY( ++ m_cycle, m_level, ++ kahip_task5_oracle_trace::refinement_epoch); + config.total_num_labels = config.k; + config.upper_bound_cluster = config.upper_bound_partition; + +@@ -431,4 +445,3 @@ void distributed_partitioner::check( MPI_Comm communicator, PPartitionConfig & c + + MPI_Barrier(communicator); + } +- +diff --git a/parallel/parallel_src/lib/parallel_contraction_projection/parallel_block_down_propagation.cpp b/parallel/parallel_src/lib/parallel_contraction_projection/parallel_block_down_propagation.cpp +index e45b135..d3a3bea 100644 +--- a/parallel/parallel_src/lib/parallel_contraction_projection/parallel_block_down_propagation.cpp ++++ b/parallel/parallel_src/lib/parallel_contraction_projection/parallel_block_down_propagation.cpp +@@ -6,6 +6,7 @@ + *****************************************************************************/ + + #include "parallel_block_down_propagation.h" ++#include "communication/task5_oracle_trace.h" + + parallel_block_down_propagation::parallel_block_down_propagation() { + +@@ -95,6 +96,16 @@ void parallel_block_down_propagation::propagate_block_down( MPI_Comm communicato + } + } + ++#if KAHIP_ENABLE_TASK5_ORACLE_TRACE ++ forall_local_nodes(Q, node) { ++ KAHIP_TASK5_ORACLE_TRACE( ++ kahip_task5_oracle_trace::block_propagation( ++ kahip_task5_oracle_trace::current_hierarchy(), ++ Q.getGlobalID(node), rank, rank, ++ Q.getSecondPartitionIndex(node))); ++ } endfor ++#endif ++ + update_ghost_nodes_blocks( communicator, Q ); + } + +@@ -164,6 +175,10 @@ void parallel_block_down_propagation::update_ghost_nodes_blocks( MPI_Comm commun + NodeWeight block = message[i+1]; + + G.setSecondPartitionIndex( G.getLocalID(global_id), block ); ++ KAHIP_TASK5_ORACLE_TRACE( ++ kahip_task5_oracle_trace::block_propagation( ++ kahip_task5_oracle_trace::current_hierarchy(), ++ global_id, st.MPI_SOURCE, rank, block)); + } + } + +diff --git a/parallel/parallel_src/lib/parallel_contraction_projection/parallel_contraction.cpp b/parallel/parallel_src/lib/parallel_contraction_projection/parallel_contraction.cpp +index 83cf67b..85c5a36 100644 +--- a/parallel/parallel_src/lib/parallel_contraction_projection/parallel_contraction.cpp ++++ b/parallel/parallel_src/lib/parallel_contraction_projection/parallel_contraction.cpp +@@ -6,6 +6,7 @@ + *****************************************************************************/ + + #include "parallel_contraction.h" ++#include "communication/task5_oracle_trace.h" + #include "data_structure/hashed_graph.h" + #include "tools/helpers.h" + +@@ -17,9 +18,13 @@ parallel_contraction::~parallel_contraction() { + + } + +-void parallel_contraction::contract_to_distributed_quotient( MPI_Comm communicator, PPartitionConfig & config, ++void parallel_contraction::contract_to_distributed_quotient( MPI_Comm communicator, PPartitionConfig & config, + parallel_graph_access & G, + parallel_graph_access & Q) { ++#if KAHIP_ENABLE_TASK5_ORACLE_TRACE ++ PEID trace_rank = 0; ++ MPI_Comm_rank(communicator, &trace_rank); ++#endif + + NodeID number_of_distinct_labels; // equals global number of coarse nodes + +@@ -33,6 +38,11 @@ void parallel_contraction::contract_to_distributed_quotient( MPI_Comm communicat + G.allocate_node_to_cnode(); + forall_local_nodes(G, node) { + G.setCNode( node, label_mapping[ G.getNodeLabel( node )]); ++ KAHIP_TASK5_ORACLE_TRACE( ++ kahip_task5_oracle_trace::contraction_label( ++ kahip_task5_oracle_trace::current_hierarchy(), ++ G.getGlobalID(node), trace_rank, G.getNodeLabel(node), ++ G.getCNode(node))); + } endfor + + get_nodes_to_cnodes_ghost_nodes( communicator, G ); +@@ -54,6 +64,24 @@ void parallel_contraction::contract_to_distributed_quotient( MPI_Comm communicat + + redistribute_hased_graph_and_build_graph_locally( communicator, hG, node_weights, number_of_distinct_labels, Q ); + update_ghost_nodes_weights( communicator, Q ); ++#if KAHIP_ENABLE_TASK5_ORACLE_TRACE ++ forall_local_nodes(Q, node) { ++ KAHIP_TASK5_ORACLE_TRACE( ++ kahip_task5_oracle_trace::quotient_node_weight( ++ kahip_task5_oracle_trace::current_hierarchy(), ++ Q.getGlobalID(node), trace_rank, ++ Q.getNodeWeight(node))); ++ forall_out_edges(Q, edge, node) { ++ NodeID target = Q.getEdgeTarget(edge); ++ KAHIP_TASK5_ORACLE_TRACE( ++ kahip_task5_oracle_trace::quotient_edge( ++ kahip_task5_oracle_trace::current_hierarchy(), ++ Q.getGlobalID(node), trace_rank, ++ Q.getGlobalID(target), ++ Q.getEdgeWeight(edge))); ++ } endfor ++ } endfor ++#endif + } + + void parallel_contraction::compute_label_mapping( MPI_Comm communicator, parallel_graph_access & G, +diff --git a/parallel/parallel_src/lib/parallel_contraction_projection/parallel_projection.cpp b/parallel/parallel_src/lib/parallel_contraction_projection/parallel_projection.cpp +index 7003251..3fe1314 100644 +--- a/parallel/parallel_src/lib/parallel_contraction_projection/parallel_projection.cpp ++++ b/parallel/parallel_src/lib/parallel_contraction_projection/parallel_projection.cpp +@@ -6,6 +6,7 @@ + *****************************************************************************/ + + #include "parallel_projection.h" ++#include "communication/task5_oracle_trace.h" + + parallel_projection::parallel_projection() { + +@@ -26,6 +27,9 @@ void parallel_projection::parallel_project( MPI_Comm communicator, parallel_grap + m_messages.resize(size); + + std::unordered_map< NodeID, std::vector< NodeID > > cnode_to_nodes; ++#if KAHIP_ENABLE_TASK5_ORACLE_TRACE ++ std::unordered_map< NodeID, NodeID > request_id_by_cnode; ++#endif + forall_local_nodes(finer, node) { + NodeID cnode = finer.getCNode(node); + //std::cout << "cnode " << cnode << std::endl; +@@ -38,6 +42,13 @@ void parallel_projection::parallel_project( MPI_Comm communicator, parallel_grap + + if( cnode_to_nodes.find( cnode ) == cnode_to_nodes.end()) { + m_messages[peID].push_back(cnode); // we are requesting the label of this node ++#if KAHIP_ENABLE_TASK5_ORACLE_TRACE ++ request_id_by_cnode[cnode] = finer.getGlobalID(node); ++#endif ++ KAHIP_TASK5_ORACLE_TRACE( ++ kahip_task5_oracle_trace::projection_request( ++ kahip_task5_oracle_trace::current_hierarchy(), ++ finer.getGlobalID(node), rank, peID, cnode)); + } + + cnode_to_nodes[cnode].push_back(node); +@@ -125,6 +136,11 @@ void parallel_projection::parallel_project( MPI_Comm communicator, parallel_grap + for( ULONG i = 0; i < (ULONG)incmessage.size(); i++) { + std::vector< NodeID > & proj = cnode_to_nodes[m_messages[peID][i]]; + NodeID label = incmessage[i]; ++ KAHIP_TASK5_ORACLE_TRACE( ++ kahip_task5_oracle_trace::projection_reply( ++ kahip_task5_oracle_trace::current_hierarchy(), ++ request_id_by_cnode[m_messages[peID][i]], ++ rank, peID, m_messages[peID][i], label)); + + for( ULONG j = 0; j < proj.size(); j++) { + finer.setNodeLabel(proj[j], label); +diff --git a/parallel/parallel_src/lib/parallel_label_compress/parallel_label_compress.h b/parallel/parallel_src/lib/parallel_label_compress/parallel_label_compress.h +index 28fd787..39d441f 100644 +--- a/parallel/parallel_src/lib/parallel_label_compress/parallel_label_compress.h ++++ b/parallel/parallel_src/lib/parallel_label_compress/parallel_label_compress.h +@@ -39,6 +39,7 @@ class parallel_label_compress { + hmap_wrapper< T > hash_map(config); + hash_map.init( G.get_max_degree() ); + for( ULONG i = 0; i < config.label_iterations; i++) { ++ KAHIP_TASK5_ORACLE_SET_ITERATION(i); + NodeID prev_node = 0; + forall_local_nodes(G, rnode) { + NodeID node = permutation[rnode]; // use the current random node diff --git a/parallel/parallel_src/tests/initial_partitioning/bipartition_candidate_test.cpp b/parallel/parallel_src/tests/initial_partitioning/bipartition_candidate_test.cpp new file mode 100644 index 00000000..7f582508 --- /dev/null +++ b/parallel/parallel_src/tests/initial_partitioning/bipartition_candidate_test.cpp @@ -0,0 +1,75 @@ +#include +#include +#include +#include +#include + +#include "partition/initial_partitioning/bipartition_candidate.h" + +namespace { +namespace candidate = kahip::initial_partitioning; + +void require(bool condition, std::string_view diagnostic) { + if (!condition) { + std::cerr << diagnostic << '\n'; + std::exit(EXIT_FAILURE); + } +} +} // namespace + +int main() { + auto const targets = candidate::bipartition_targets{100, 100}; + + // This is the exact shape hidden by the historic + // `lhs_overload + rhs_block_weight` typo: equal-cut challenger B has lower + // total overload, but its raw RHS weight is necessarily larger than A's + // overload and therefore could never replace A. + auto const typo_incumbent = candidate::make_bipartition_candidate( + 7, 100, 110, targets, 3, 3, true, true, 0); + auto const typo_challenger = candidate::make_bipartition_candidate( + 7, 100, 105, targets, 3, 3, true, true, 1); + require(candidate::is_better_bipartition_candidate(typo_challenger, + typo_incumbent), + "equal-cut candidates must compare total overload, not block weight"); + + auto const lower_cut_infeasible = candidate::make_bipartition_candidate( + 1, 101, 100, targets, 3, 3, true, true, 0); + auto const higher_cut_feasible = candidate::make_bipartition_candidate( + 2, 100, 100, targets, 3, 3, true, true, 1); + require(candidate::is_better_bipartition_candidate(lower_cut_infeasible, + higher_cut_feasible), + "growth targets must not replace KaHIP's cut-first objective"); + + auto const lower_overload = candidate::make_bipartition_candidate( + 9, 103, 100, targets, 3, 3, true, true, 1); + auto const lower_cut = candidate::make_bipartition_candidate( + 2, 104, 100, targets, 3, 3, true, true, 2); + require(candidate::is_better_bipartition_candidate(lower_cut, lower_overload), + "total overload must only break equal-cut candidates"); + + auto const invalid_empty_block = candidate::make_bipartition_candidate( + 0, 100, 0, targets, 6, 0, true, true, 0); + require(candidate::is_better_bipartition_candidate(higher_cut_feasible, + invalid_empty_block), + "a mathematically invalid empty block must never beat a valid split"); + + auto const tie_targets = candidate::bipartition_targets{110, 110}; + auto const balanced_tie = candidate::make_bipartition_candidate( + 2, 99, 101, tie_targets, 3, 3, true, true, 3); + auto const imbalanced_tie = candidate::make_bipartition_candidate( + 2, 98, 102, tie_targets, 3, 3, true, true, 2); + require(candidate::is_better_bipartition_candidate(imbalanced_tie, + balanced_tie), + "equal cut and overload must retain deterministic trial order"); + + auto const too_short = std::array{100}; + auto const negative = std::array{100, -1}; + auto const valid = std::array{100, 120, 999}; + require(!candidate::validated_bipartition_targets(too_short).has_value(), + "one target weight must be rejected before indexing"); + require(!candidate::validated_bipartition_targets(negative).has_value(), + "negative target weights must be rejected"); + require(candidate::validated_bipartition_targets(valid) == + candidate::bipartition_targets{100, 120}, + "the first two valid target weights must be preserved exactly"); +} diff --git a/parallel/parallel_src/tests/initial_partitioning/bipartition_invariant_cases.h b/parallel/parallel_src/tests/initial_partitioning/bipartition_invariant_cases.h new file mode 100644 index 00000000..9a1b61f9 --- /dev/null +++ b/parallel/parallel_src/tests/initial_partitioning/bipartition_invariant_cases.h @@ -0,0 +1,138 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace kahip::test { + +enum class bipartition_growth { bfs, fm }; + +struct growth_result final { + std::vector labels; + std::array node_counts{}; + std::array block_weights{}; + + friend auto operator==(growth_result const&, growth_result const&) + -> bool = default; +}; + +template +[[nodiscard]] auto run_growth(bipartition_growth algorithm, + std::span weights, + std::span const> adjacency, + unsigned grow_target) -> growth_result { + REQUIRE(weights.size() == adjacency.size()); + + auto graph = typename Adapter::graph_type{}; + auto const edge_count = std::transform_reduce( + adjacency.begin(), adjacency.end(), std::size_t{0}, std::plus<>{}, + [](auto const& neighbors) { return neighbors.size(); }); + graph.start_construction(static_cast(weights.size()), + static_cast(edge_count)); + for (auto source = std::size_t{0}; source < weights.size(); ++source) { + auto const node = graph.new_node(); + REQUIRE(node == source); + graph.setNodeWeight(node, weights[source]); + for (auto const target : adjacency[source]) { + auto const edge = graph.new_edge(node, target); + graph.setEdgeWeight(edge, 1); + } + } + graph.finish_construction(); + + auto config = typename Adapter::config_type{}; + Adapter::configure(config, algorithm, grow_target); + Adapter::reset_seed(0); + auto partitioner = typename Adapter::partitioner_type{}; + Adapter::grow(partitioner, algorithm, config, graph); + + auto result = growth_result{}; + result.labels.reserve(weights.size()); + for (auto node = std::size_t{0}; node < weights.size(); ++node) { + auto const block = graph.getPartitionIndex(static_cast(node)); + REQUIRE(block < 2); + result.labels.push_back(block); + ++result.node_counts[block]; + result.block_weights[block] += weights[node]; + } + return result; +} + +template +[[nodiscard]] auto require_deterministic_growth( + bipartition_growth algorithm, + std::span weights, + std::span const> adjacency, + unsigned grow_target) -> growth_result { + auto const first = + run_growth(algorithm, weights, adjacency, grow_target); + auto const second = + run_growth(algorithm, weights, adjacency, grow_target); + REQUIRE(second == first); + return first; +} + +template +void require_singleton_rhs(bipartition_growth algorithm) { + auto const weights = std::array{5U}; + auto const adjacency = std::array{std::vector{}}; + + auto const result = + require_deterministic_growth(algorithm, weights, adjacency, 5U); + + REQUIRE(result.labels == std::vector{1U}); + REQUIRE(result.node_counts == std::array{0U, 1U}); + REQUIRE(result.block_weights == std::array{0U, 5U}); +} + +template +void require_weighted_pair_exact_target(bipartition_growth algorithm) { + auto const weights = std::array{7U, 7U}; + auto const adjacency = + std::array{std::vector{1U}, std::vector{0U}}; + + auto const result = + require_deterministic_growth(algorithm, weights, adjacency, 7U); + + REQUIRE(result.node_counts == std::array{1U, 1U}); + REQUIRE(result.block_weights == std::array{7U, 7U}); +} + +template +void require_two_rhs_vertices_at_target(bipartition_growth algorithm) { + auto const weights = std::array{1U, 1U, 1U, 1U, 1U}; + auto const adjacency = + std::array{std::vector{1U}, std::vector{0U, 2U}, + std::vector{1U, 3U}, std::vector{2U, 4U}, + std::vector{3U}}; + + auto const result = + require_deterministic_growth(algorithm, weights, adjacency, 3U); + + REQUIRE(result.node_counts == std::array{3U, 2U}); + REQUIRE(result.block_weights == std::array{3U, 2U}); +} + +template +void require_disconnected_restart_reaches_target(bipartition_growth algorithm) { + auto const weights = std::array{1U, 1U, 1U, 1U}; + auto const adjacency = + std::array{std::vector{1U}, std::vector{0U}, + std::vector{3U}, std::vector{2U}}; + + auto const result = + require_deterministic_growth(algorithm, weights, adjacency, 3U); + + REQUIRE(result.node_counts == std::array{3U, 1U}); + REQUIRE(result.block_weights == std::array{3U, 1U}); +} + +} // namespace kahip::test diff --git a/parallel/parallel_src/tests/initial_partitioning/modified_bipartition_invariant_test.cpp b/parallel/parallel_src/tests/initial_partitioning/modified_bipartition_invariant_test.cpp new file mode 100644 index 00000000..43bddfd0 --- /dev/null +++ b/parallel/parallel_src/tests/initial_partitioning/modified_bipartition_invariant_test.cpp @@ -0,0 +1,86 @@ +#include + +#include "partition/initial_partitioning/bipartition.h" +#include "tools/random_functions.h" + +#include "bipartition_invariant_cases.h" + +namespace kahip::modified { +// Keep this direct test independent of post-growth refinement so it protects +// the modified KaHIP counter itself. +struct bipartition_invariant_test_access final { + using graph_type = graph_access; + using config_type = PartitionConfig; + using partitioner_type = bipartition; + + static void configure(config_type& config, + kahip::test::bipartition_growth algorithm, + unsigned grow_target) { + config.buffoon = false; + config.grow_target = static_cast(grow_target); + config.bipartition_algorithm = + algorithm == kahip::test::bipartition_growth::bfs ? BIPARTITION_BFS + : BIPARTITION_FM; + } + + static void reset_seed(int seed) { random_functions::setSeed(seed); } + + static void grow(partitioner_type& partitioner, + kahip::test::bipartition_growth algorithm, + config_type const& config, + graph_type& graph) { + if (algorithm == kahip::test::bipartition_growth::bfs) { + partitioner.grow_regions_bfs(config, graph); + } else { + partitioner.grow_regions_fm(config, graph); + } + } +}; +} // namespace kahip::modified + +using modified_bipartition_adapter = + kahip::modified::bipartition_invariant_test_access; + +TEST_CASE("modified bipartition leaves a singleton on the RHS") { + SECTION("BFS") { + kahip::test::require_singleton_rhs( + kahip::test::bipartition_growth::bfs); + } + SECTION("FM") { + kahip::test::require_singleton_rhs( + kahip::test::bipartition_growth::fm); + } +} + +TEST_CASE("modified bipartition assigns a weighted pair at the exact target") { + SECTION("BFS") { + kahip::test::require_weighted_pair_exact_target< + modified_bipartition_adapter>(kahip::test::bipartition_growth::bfs); + } + SECTION("FM") { + kahip::test::require_weighted_pair_exact_target< + modified_bipartition_adapter>(kahip::test::bipartition_growth::fm); + } +} + +TEST_CASE("modified bipartition reaches its target with two RHS vertices") { + SECTION("BFS") { + kahip::test::require_two_rhs_vertices_at_target< + modified_bipartition_adapter>(kahip::test::bipartition_growth::bfs); + } + SECTION("FM") { + kahip::test::require_two_rhs_vertices_at_target< + modified_bipartition_adapter>(kahip::test::bipartition_growth::fm); + } +} + +TEST_CASE("modified bipartition restarts across disconnected components") { + SECTION("BFS") { + kahip::test::require_disconnected_restart_reaches_target< + modified_bipartition_adapter>(kahip::test::bipartition_growth::bfs); + } + SECTION("FM") { + kahip::test::require_disconnected_restart_reaches_target< + modified_bipartition_adapter>(kahip::test::bipartition_growth::fm); + } +} diff --git a/parallel/parallel_src/tests/initial_partitioning/root_bipartition_invariant_test.cpp b/parallel/parallel_src/tests/initial_partitioning/root_bipartition_invariant_test.cpp new file mode 100644 index 00000000..c0ac6c6b --- /dev/null +++ b/parallel/parallel_src/tests/initial_partitioning/root_bipartition_invariant_test.cpp @@ -0,0 +1,85 @@ +#include + +#include "partition/initial_partitioning/bipartition.h" +#include "tools/random_functions.h" + +#include "bipartition_invariant_cases.h" + +// The internal friend keeps this test on the real growth routines so later FM +// refinement cannot hide a broken assignment counter. +struct bipartition_invariant_test_access final { + using graph_type = graph_access; + using config_type = PartitionConfig; + using partitioner_type = bipartition; + + static void configure(config_type& config, + kahip::test::bipartition_growth algorithm, + unsigned grow_target) { + config.buffoon = false; + config.connected_blocks = false; + config.grow_target = static_cast(grow_target); + config.bipartition_algorithm = + algorithm == kahip::test::bipartition_growth::bfs ? BIPARTITION_BFS + : BIPARTITION_FM; + } + + static void reset_seed(int seed) { random_functions::setSeed(seed); } + + static void grow(partitioner_type& partitioner, + kahip::test::bipartition_growth algorithm, + config_type const& config, + graph_type& graph) { + if (algorithm == kahip::test::bipartition_growth::bfs) { + partitioner.grow_regions_bfs(config, graph); + } else { + partitioner.grow_regions_fm(config, graph); + } + } +}; + +TEST_CASE("root bipartition leaves a singleton on the RHS") { + SECTION("BFS") { + kahip::test::require_singleton_rhs( + kahip::test::bipartition_growth::bfs); + } + SECTION("FM") { + kahip::test::require_singleton_rhs( + kahip::test::bipartition_growth::fm); + } +} + +TEST_CASE("root bipartition assigns a weighted pair at the exact target") { + SECTION("BFS") { + kahip::test::require_weighted_pair_exact_target< + bipartition_invariant_test_access>( + kahip::test::bipartition_growth::bfs); + } + SECTION("FM") { + kahip::test::require_weighted_pair_exact_target< + bipartition_invariant_test_access>(kahip::test::bipartition_growth::fm); + } +} + +TEST_CASE("root bipartition reaches its target with two RHS vertices") { + SECTION("BFS") { + kahip::test::require_two_rhs_vertices_at_target< + bipartition_invariant_test_access>( + kahip::test::bipartition_growth::bfs); + } + SECTION("FM") { + kahip::test::require_two_rhs_vertices_at_target< + bipartition_invariant_test_access>(kahip::test::bipartition_growth::fm); + } +} + +TEST_CASE("root bipartition restarts across disconnected components") { + SECTION("BFS") { + kahip::test::require_disconnected_restart_reaches_target< + bipartition_invariant_test_access>( + kahip::test::bipartition_growth::bfs); + } + SECTION("FM") { + kahip::test::require_disconnected_restart_reaches_target< + bipartition_invariant_test_access>(kahip::test::bipartition_growth::fm); + } +} diff --git a/parallel/parallel_src/tests/interface/kaffpae_c_boundary_failure_probe.cpp b/parallel/parallel_src/tests/interface/kaffpae_c_boundary_failure_probe.cpp new file mode 100644 index 00000000..e6ea1c04 --- /dev/null +++ b/parallel/parallel_src/tests/interface/kaffpae_c_boundary_failure_probe.cpp @@ -0,0 +1,101 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kaHIP_interface.h" +#include "tools/fatal_diagnostics.h" + +namespace { +std::atomic_bool fail_next_allocation = false; +volatile std::sig_atomic_t diagnostic_was_flushed = 0; + +void write_text(std::string_view text) noexcept { + static_cast(::write(STDERR_FILENO, text.data(), text.size())); +} + +void observe_diagnostic(std::string_view message) noexcept { + write_text(message); + write_text("\n"); +} + +void observe_flush() noexcept { + diagnostic_was_flushed = 1; + write_text("observed synchronous diagnostic flush\n"); +} + +constexpr auto observing_sink = kahip::diagnostics::sink{ + .write = observe_diagnostic, + .flush = observe_flush, +}; +} // namespace + +void* operator new(std::size_t size) { + if (fail_next_allocation.exchange(false)) { + throw std::bad_alloc{}; + } + if (auto* storage = std::malloc(size); storage != nullptr) { + return storage; + } + throw std::bad_alloc{}; +} + +void operator delete(void* storage) noexcept { std::free(storage); } + +void operator delete(void* storage, std::size_t) noexcept { + std::free(storage); +} + +extern "C" int MPI_Abort(MPI_Comm communicator, int) { + if (diagnostic_was_flushed == 0) { + write_text("kaffpaE boundary aborted before diagnostic flush\n"); + std::_Exit(91); + } + auto comparison = int{MPI_UNEQUAL}; + if (communicator == MPI_COMM_NULL || + PMPI_Comm_compare(MPI_COMM_WORLD, communicator, &comparison) != + MPI_SUCCESS || + comparison != MPI_IDENT) { + write_text("kaffpaE boundary aborted the wrong communicator\n"); + std::_Exit(92); + } + write_text("observed kaffpaE communicator abort after diagnostic flush\n"); + std::_Exit(86); +} + +int main(int argc, char** argv) { + if (argc != 2 || std::string_view{argv[1]} != "allocation") { + std::fputs("usage: kaffpae_c_boundary_failure_probe allocation\n", stderr); + return 64; + } + if (PMPI_Init(&argc, &argv) != MPI_SUCCESS) { + std::fputs("could not initialize MPI\n", stderr); + return 70; + } + + static_cast( + kahip::diagnostics::exchange_sink_for_testing(&observing_sink)); + + auto n = 1; + auto xadj = std::array{0, 0}; + auto ignored_adjacency = 0; + auto nparts = 1; + auto imbalance = 0.03; + auto edgecut = 0; + auto balance = 0.0; + auto partition = std::array{0}; + fail_next_allocation = true; + kaffpaE(&n, nullptr, xadj.data(), nullptr, &ignored_adjacency, &nparts, + &imbalance, false, false, 0, 1, ECO, MPI_COMM_WORLD, &edgecut, + &balance, partition.data()); + + std::fputs("kaffpaE returned after an injected exception\n", stderr); + return 71; +} diff --git a/parallel/parallel_src/tests/interface/parhip_abort_marker_count_validation_test.cmake b/parallel/parallel_src/tests/interface/parhip_abort_marker_count_validation_test.cmake new file mode 100644 index 00000000..77311438 --- /dev/null +++ b/parallel/parallel_src/tests/interface/parhip_abort_marker_count_validation_test.cmake @@ -0,0 +1,49 @@ +cmake_minimum_required(VERSION 4.0) + +if( + NOT DEFINED VERIFIER + OR NOT DEFINED FAKE_LAUNCHER + OR NOT DEFINED WORK_DIRECTORY +) + message( + FATAL_ERROR + "VERIFIER, FAKE_LAUNCHER, and WORK_DIRECTORY are required" + ) +endif() + +file(MAKE_DIRECTORY "${WORK_DIRECTORY}") +execute_process( + COMMAND + "${CMAKE_COMMAND}" + "-DMPIEXEC_EXECUTABLE=${FAKE_LAUNCHER}" + "-DMPIEXEC_NUMPROC_FLAG=--ranks" + "-DPROBE=parhip-interface" + "-DMODE=zero-k" + "-DEXPECTED_DIAGNOSTIC=MPI adapter programming failure: synthetic ParHIP diagnostic" + -P + "${VERIFIER}" + RESULT_VARIABLE verifier_result + OUTPUT_VARIABLE verifier_stdout + ERROR_VARIABLE verifier_stderr + TIMEOUT 5 +) + +set(verifier_output "${verifier_stdout}\n${verifier_stderr}") +if("${verifier_result}" STREQUAL "0") + message( + FATAL_ERROR + "ParHIP verifier accepted a single abort marker\n${verifier_output}" + ) +endif() +string( + FIND + "${verifier_output}" + "expected exactly 2 affected-communicator abort markers; found 1" + marker_count_diagnostic +) +if(marker_count_diagnostic EQUAL -1) + message( + FATAL_ERROR + "ParHIP verifier failed for the wrong reason\n${verifier_output}" + ) +endif() diff --git a/parallel/parallel_src/tests/interface/parhip_abort_marker_fake_launcher.cpp b/parallel/parallel_src/tests/interface/parhip_abort_marker_fake_launcher.cpp new file mode 100644 index 00000000..a1fe1a61 --- /dev/null +++ b/parallel/parallel_src/tests/interface/parhip_abort_marker_fake_launcher.cpp @@ -0,0 +1,7 @@ +#include + +int main() { + std::cerr << "observed ParHIP interface MPI_Abort on affected communicator\n" + << "MPI adapter programming failure: synthetic ParHIP diagnostic\n"; + return 86; +} diff --git a/parallel/parallel_src/tests/interface/parhip_build_tree_target_consumer.cpp b/parallel/parallel_src/tests/interface/parhip_build_tree_target_consumer.cpp new file mode 100644 index 00000000..fa7934b6 --- /dev/null +++ b/parallel/parallel_src/tests/interface/parhip_build_tree_target_consumer.cpp @@ -0,0 +1,7 @@ +#include + +int main() { + auto initialized = 0; + return MPI_Initialized(&initialized) == MPI_SUCCESS && initialized == 0 ? 0 + : 1; +} diff --git a/parallel/parallel_src/tests/interface/parhip_interface_determinism_probe.cpp b/parallel/parallel_src/tests/interface/parhip_interface_determinism_probe.cpp new file mode 100644 index 00000000..9f4a1c6a --- /dev/null +++ b/parallel/parallel_src/tests/interface/parhip_interface_determinism_probe.cpp @@ -0,0 +1,176 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "fixtures/cube_graph.h" +#include "parhip_interface.h" + +namespace { +struct local_graph final { + std::vector distribution; + std::vector offsets; + std::vector neighbors; +}; + +[[nodiscard]] auto make_cube(int rank, int size) -> local_graph { + auto const cube = parhip::testing::cube_graph{{10, 10, 10}}; + auto graph = local_graph{}; + graph.distribution.resize(static_cast(size) + 1); + for (auto index = 0; index <= size; ++index) { + graph.distribution[static_cast(index)] = + static_cast(index) * cube.vertex_count() / + static_cast(size); + } + + auto const first = graph.distribution[static_cast(rank)]; + auto const last = graph.distribution[static_cast(rank) + 1]; + graph.offsets.reserve(static_cast(last - first) + 1); + graph.offsets.push_back(0); + for (auto vertex = first; vertex < last; ++vertex) { + auto const adjacent = cube.neighbors(vertex); + graph.neighbors.insert(graph.neighbors.end(), adjacent.begin(), + adjacent.end()); + graph.offsets.push_back(static_cast(graph.neighbors.size())); + } + return graph; +} + +struct partition_result final { + int edge_cut; + std::vector global_partition; +}; + +[[nodiscard]] auto partition_cube(local_graph& graph, + int rank, + int size, + int seed, + int mode, + MPI_Comm communicator) -> partition_result { + auto const local_count = + graph.distribution[static_cast(rank) + 1] - + graph.distribution[static_cast(rank)]; + auto local_partition = + std::vector(static_cast(local_count)); + auto blocks = 4; + auto imbalance = 0.03; + auto edge_cut = -1; + auto mutable_communicator = communicator; + ParHIPPartitionKWay(graph.distribution.data(), graph.offsets.data(), + graph.neighbors.data(), nullptr, nullptr, &blocks, + &imbalance, true, seed, mode, &edge_cut, + local_partition.data(), &mutable_communicator); + + auto local_valid = + edge_cut >= 0 && std::ranges::all_of(local_partition, [](idxtype block) { + return block < idxtype{4}; + }); + auto all_valid = 0; + auto const encoded_valid = local_valid ? 1 : 0; + if (MPI_Allreduce(&encoded_valid, &all_valid, 1, MPI_INT, MPI_MIN, + communicator) != MPI_SUCCESS || + all_valid == 0) { + std::_Exit(11); + } + + auto counts = std::vector(static_cast(size)); + auto displacements = std::vector(static_cast(size)); + for (auto index = 0; index < size; ++index) { + auto const begin = graph.distribution[static_cast(index)]; + auto const end = graph.distribution[static_cast(index) + 1]; + if (!std::in_range(begin) || !std::in_range(end - begin)) { + std::_Exit(12); + } + counts[static_cast(index)] = static_cast(end - begin); + displacements[static_cast(index)] = static_cast(begin); + } + + auto global_partition = + std::vector(static_cast(graph.distribution.back())); + if (MPI_Allgatherv(local_partition.data(), static_cast(local_count), + MPI_UNSIGNED_LONG_LONG, global_partition.data(), + counts.data(), displacements.data(), + MPI_UNSIGNED_LONG_LONG, communicator) != MPI_SUCCESS) { + std::_Exit(13); + } + return {.edge_cut = edge_cut, + .global_partition = std::move(global_partition)}; +} + +void print_target(partition_result const& result) { + std::cout << "PARHIP_TARGET " << result.edge_cut; + for (auto const block : result.global_partition) { + std::cout << ' ' << block; + } + std::cout << '\n'; +} +} // namespace + +int main(int argc, char** argv) { + if (argc != 2 || MPI_Init(&argc, &argv) != MPI_SUCCESS) { + return 2; + } + auto world_rank = -1; + auto world_size = 0; + if (MPI_Comm_rank(MPI_COMM_WORLD, &world_rank) != MPI_SUCCESS || + MPI_Comm_size(MPI_COMM_WORLD, &world_size) != MPI_SUCCESS || + world_size != 2) { + return 3; + } + auto communicator = MPI_COMM_NULL; + if (MPI_Comm_split(MPI_COMM_WORLD, 0, world_size - world_rank, + &communicator) != MPI_SUCCESS || + communicator == MPI_COMM_NULL || + MPI_Comm_set_errhandler(communicator, MPI_ERRORS_RETURN) != MPI_SUCCESS) { + return 4; + } + auto rank = -1; + auto size = 0; + if (MPI_Comm_rank(communicator, &rank) != MPI_SUCCESS || + MPI_Comm_size(communicator, &size) != MPI_SUCCESS) { + return 5; + } + + auto graph = make_cube(rank, size); + auto const selected = std::string_view{argv[1]}; + if (selected == "fresh") { + auto const target = + partition_cube(graph, rank, size, 1, FASTMESH, communicator); + if (rank == 0) + print_target(target); + } else if (selected == "contaminated") { + static_cast( + partition_cube(graph, rank, size, 7919, ULTRAFASTMESH, communicator)); + auto const first_target = + partition_cube(graph, rank, size, 1, FASTMESH, communicator); + auto const second_target = + partition_cube(graph, rank, size, 1, FASTMESH, communicator); + if (rank == 0) { + print_target(first_target); + print_target(second_target); + } + } else if (selected == "wrapped") { + constexpr auto wrapped_seed = 536870912; + auto const first_target = partition_cube( + graph, rank, size, wrapped_seed, FASTMESH, communicator); + auto const second_target = partition_cube( + graph, rank, size, wrapped_seed, FASTMESH, communicator); + if (rank == 0) { + print_target(first_target); + print_target(second_target); + } + } else { + return 6; + } + + if (MPI_Comm_free(&communicator) != MPI_SUCCESS || + MPI_Finalize() != MPI_SUCCESS) { + return 7; + } + return 0; +} diff --git a/parallel/parallel_src/tests/interface/parhip_interface_failure_probe.cpp b/parallel/parallel_src/tests/interface/parhip_interface_failure_probe.cpp new file mode 100644 index 00000000..2a8938bc --- /dev/null +++ b/parallel/parallel_src/tests/interface/parhip_interface_failure_probe.cpp @@ -0,0 +1,526 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "kahip_mpi_capabilities.h" +#include "parhip_interface.h" +#include "tools/fatal_diagnostics.h" + +// KAHIP_PMPI_CALLBACK_REGION_BEGIN +namespace parhip_interface_failure_probe { +enum class mode : unsigned char { + backend_reduction, + null_distribution, + zero_k, + mismatched_k, + invalid_imbalance, + mismatched_distribution, + invalid_offsets, + missing_adjacency, + invalid_neighbor, + mismatched_vertex_weights, + missing_partition, + invalid_mode, + global_weight_overflow, + imbalanced_result, + intercommunicator, +}; + +inline bool active = false; +inline mode selected = mode::backend_reduction; +inline MPI_Comm caller_communicator = MPI_COMM_NULL; +inline MPI_Comm owned_communicator = MPI_COMM_NULL; +inline int duplications = 0; +inline int error_handler_sets = 0; +inline int owned_queries = 0; +inline int validation_reductions = 0; +inline int allgathers = 0; +inline int finalizations = 0; +inline int operation_rank = -1; +inline int detail_writes = 0; +inline int detail_flushes = 0; +inline bool detail_flushed = false; +inline int balance_ordering_completions = 0; +inline bool balance_ordering_completed = false; +inline bool callback_error = false; + +void write_text(std::string_view text) noexcept { + static_cast(::write(STDERR_FILENO, text.data(), text.size())); +} + +void write_detail(std::string_view text) noexcept { + ++detail_writes; + auto const rank = operation_rank == 0 ? '0' : '1'; + auto const prefix = std::string_view{"PARHIP_DETAIL rank="}; + auto const separator = std::string_view{" "}; + auto const newline = std::string_view{"\n"}; + auto const vectors = std::array{ + iovec{.iov_base = const_cast(prefix.data()), + .iov_len = prefix.size()}, + iovec{.iov_base = const_cast(&rank), .iov_len = 1}, + iovec{.iov_base = const_cast(separator.data()), + .iov_len = separator.size()}, + iovec{.iov_base = const_cast(text.data()), .iov_len = text.size()}, + iovec{.iov_base = const_cast(newline.data()), + .iov_len = newline.size()}, + }; + static_cast(::writev(STDERR_FILENO, vectors.data(), vectors.size())); +} + +void flush_detail() noexcept { + ++detail_flushes; + detail_flushed = true; + auto const marker = operation_rank == 0 + ? std::string_view{"PARHIP_DETAIL_FLUSH rank=0\n"} + : std::string_view{"PARHIP_DETAIL_FLUSH rank=1\n"}; + static_cast(::write(STDERR_FILENO, marker.data(), marker.size())); +} + +inline constexpr auto detail_sink = kahip::diagnostics::sink{ + .write = write_detail, + .flush = flush_detail, +}; + +[[nodiscard]] auto expects_duplicate() noexcept -> bool { + return selected != mode::intercommunicator; +} + +[[nodiscard]] auto exercises_full_algorithm() noexcept -> bool { + return selected == mode::imbalanced_result; +} + +[[nodiscard]] auto expected_abort_state() noexcept -> bool { + if (callback_error || finalizations != 0) { + return false; + } + if (!expects_duplicate()) { + return duplications == 0 && error_handler_sets == 0 && owned_queries == 1 && + validation_reductions == 0; + } + if (exercises_full_algorithm()) { + auto const detail_state = operation_rank == 0 + ? detail_writes == 1 && detail_flushes == 1 && + detail_flushed + : detail_writes == 0 && detail_flushes == 0 && + !detail_flushed; + return duplications >= 1 && error_handler_sets >= 1 && owned_queries >= 2 && + owned_communicator != MPI_COMM_NULL && validation_reductions >= 1 && + detail_state && balance_ordering_completions == 1 && + balance_ordering_completed; + } + auto const avoided_rank_count_storage = + selected != mode::global_weight_overflow || allgathers == 0; + return duplications == 1 && error_handler_sets == 1 && + owned_communicator != MPI_COMM_NULL && validation_reductions >= 1 && + avoided_rank_count_storage; +} + +[[noreturn]] void observed_abort(MPI_Comm communicator, + int error_code) noexcept { + auto const expected = + expects_duplicate() ? owned_communicator : caller_communicator; + auto relation = int{MPI_UNEQUAL}; + if (error_code != EXIT_FAILURE || expected == MPI_COMM_NULL || + PMPI_Comm_compare(communicator, expected, &relation) != MPI_SUCCESS || + relation != MPI_IDENT || !expected_abort_state()) { + write_text("observed ParHIP interface MPI_Abort with unexpected state\n"); + std::_Exit(91); + } + write_text("observed ParHIP interface MPI_Abort on affected communicator\n"); + std::_Exit(86); +} +} // namespace parhip_interface_failure_probe + +static_assert(noexcept(parhip_interface_failure_probe::write_text({}))); +static_assert(noexcept(parhip_interface_failure_probe::write_detail({}))); +static_assert(noexcept(parhip_interface_failure_probe::flush_detail())); +static_assert(noexcept(parhip_interface_failure_probe::expects_duplicate())); +static_assert( + noexcept(parhip_interface_failure_probe::exercises_full_algorithm())); +static_assert(noexcept(parhip_interface_failure_probe::expected_abort_state())); +static_assert( + noexcept(parhip_interface_failure_probe::observed_abort(MPI_COMM_NULL, 0))); + +extern "C" int MPI_Comm_dup(MPI_Comm communicator, MPI_Comm* duplicate) { + using namespace parhip_interface_failure_probe; + if (!active) { + return PMPI_Comm_dup(communicator, duplicate); + } + ++duplications; + auto const first_duplicate = duplications == 1; + auto const valid_source = first_duplicate + ? communicator == caller_communicator + : exercises_full_algorithm() && + communicator != caller_communicator && + communicator != MPI_COMM_NULL; + if (!expects_duplicate() || !valid_source || duplicate == nullptr) { + callback_error = true; + return MPI_ERR_OTHER; + } + auto const result = PMPI_Comm_dup(communicator, duplicate); + if (result == MPI_SUCCESS && first_duplicate) { + owned_communicator = *duplicate; + } + return result; +} + +extern "C" int MPI_Comm_set_errhandler(MPI_Comm communicator, + MPI_Errhandler error_handler) { + using namespace parhip_interface_failure_probe; + if (!active) { + return PMPI_Comm_set_errhandler(communicator, error_handler); + } + ++error_handler_sets; + auto const valid_communicator = + communicator == owned_communicator || + (exercises_full_algorithm() && communicator != caller_communicator && + communicator != MPI_COMM_NULL); + if (!valid_communicator || error_handler != MPI_ERRORS_RETURN) { + callback_error = true; + return MPI_ERR_OTHER; + } + return PMPI_Comm_set_errhandler(communicator, error_handler); +} + +extern "C" int MPI_Comm_rank(MPI_Comm communicator, int* rank) { + using namespace parhip_interface_failure_probe; + if (active) { + ++owned_queries; + auto const diagnostic_world_query = + selected == mode::backend_reduction && communicator == MPI_COMM_WORLD; + auto const valid_communicator = + diagnostic_world_query || + (!expects_duplicate() && communicator == caller_communicator) || + communicator == owned_communicator || + (exercises_full_algorithm() && communicator != caller_communicator && + communicator != MPI_COMM_NULL); + auto const has_expected_communicator = + !expects_duplicate() || owned_communicator != MPI_COMM_NULL; + if (!has_expected_communicator || !valid_communicator) { + callback_error = true; + return MPI_ERR_OTHER; + } + } + return PMPI_Comm_rank(communicator, rank); +} + +extern "C" int MPI_Comm_size(MPI_Comm communicator, int* size) { + using namespace parhip_interface_failure_probe; + if (active) { + ++owned_queries; + auto const valid_communicator = + communicator == owned_communicator || + (exercises_full_algorithm() && communicator != caller_communicator && + communicator != MPI_COMM_NULL); + if (owned_communicator == MPI_COMM_NULL || !valid_communicator) { + callback_error = true; + return MPI_ERR_OTHER; + } + } + return PMPI_Comm_size(communicator, size); +} + +extern "C" int MPI_Allgather(void const* send_buffer, + int send_count, + MPI_Datatype send_datatype, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + using namespace parhip_interface_failure_probe; + if (active) { + ++allgathers; + } + return PMPI_Allgather(send_buffer, send_count, send_datatype, receive_buffer, + receive_count, receive_datatype, communicator); +} + +extern "C" int MPI_Allreduce(void const* send_buffer, + void* receive_buffer, + int count, + MPI_Datatype datatype, + MPI_Op operation, + MPI_Comm communicator) { + using namespace parhip_interface_failure_probe; + if (!active) { + return PMPI_Allreduce(send_buffer, receive_buffer, count, datatype, + operation, communicator); + } + ++validation_reductions; + auto const valid_communicator = + communicator == owned_communicator || + (exercises_full_algorithm() && communicator != caller_communicator && + communicator != MPI_COMM_NULL); + if (owned_communicator == MPI_COMM_NULL || !valid_communicator || + send_buffer == nullptr || receive_buffer == nullptr || + send_buffer == receive_buffer || count <= 0 || + (operation != MPI_MIN && operation != MPI_MAX && operation != MPI_BOR && + operation != MPI_SUM && operation != MPI_BAND)) { + callback_error = true; + return MPI_ERR_OTHER; + } + if (selected == mode::backend_reduction && validation_reductions == 1) { + return MPI_ERR_OTHER; + } + if (exercises_full_algorithm() && communicator == owned_communicator) { + balance_ordering_completions = 0; + balance_ordering_completed = false; + } + return PMPI_Allreduce(send_buffer, receive_buffer, count, datatype, operation, + communicator); +} + +#if KAHIP_HAVE_MPI_ALLREDUCE_C +extern "C" int MPI_Allreduce_c(void const* send_buffer, + void* receive_buffer, + MPI_Count count, + MPI_Datatype datatype, + MPI_Op operation, + MPI_Comm communicator) { + using namespace parhip_interface_failure_probe; + if (!active) { + return PMPI_Allreduce_c(send_buffer, receive_buffer, count, datatype, + operation, communicator); + } + ++validation_reductions; + auto const valid_communicator = + communicator == owned_communicator || + (exercises_full_algorithm() && communicator != caller_communicator && + communicator != MPI_COMM_NULL); + if (owned_communicator == MPI_COMM_NULL || !valid_communicator || + send_buffer == nullptr || receive_buffer == nullptr || + send_buffer == receive_buffer || count <= 0 || + (operation != MPI_MIN && operation != MPI_MAX && operation != MPI_BOR && + operation != MPI_SUM && operation != MPI_BAND)) { + callback_error = true; + return MPI_ERR_OTHER; + } + if (selected == mode::backend_reduction && validation_reductions == 1) { + return MPI_ERR_OTHER; + } + if (exercises_full_algorithm() && communicator == owned_communicator) { + balance_ordering_completions = 0; + balance_ordering_completed = false; + } + return PMPI_Allreduce_c(send_buffer, receive_buffer, count, datatype, + operation, communicator); +} +#endif + +extern "C" int MPI_Barrier(MPI_Comm communicator) { + using namespace parhip_interface_failure_probe; + if (!active) { + return PMPI_Barrier(communicator); + } + auto const tracks_balance_ordering = + exercises_full_algorithm() && communicator == owned_communicator; + auto const diagnostic_precedes_barrier = + operation_rank != 0 || + (detail_writes == 1 && detail_flushes == 1 && detail_flushed); + auto const result = PMPI_Barrier(communicator); + if (tracks_balance_ordering && result == MPI_SUCCESS) { + balance_ordering_completed = diagnostic_precedes_barrier; + balance_ordering_completions = diagnostic_precedes_barrier ? 1 : 0; + } + return result; +} + +extern "C" int MPI_Finalize() { + if (parhip_interface_failure_probe::active) { + ++parhip_interface_failure_probe::finalizations; + return MPI_SUCCESS; + } + return PMPI_Finalize(); +} + +extern "C" int MPI_Abort(MPI_Comm communicator, int error_code) { + parhip_interface_failure_probe::observed_abort(communicator, error_code); +} +// KAHIP_PMPI_CALLBACK_REGION_END + +namespace { +[[nodiscard]] auto parse_mode(std::string_view value) + -> parhip_interface_failure_probe::mode { + using mode = parhip_interface_failure_probe::mode; + if (value == "backend-reduction") + return mode::backend_reduction; + if (value == "null-distribution") + return mode::null_distribution; + if (value == "zero-k") + return mode::zero_k; + if (value == "mismatched-k") + return mode::mismatched_k; + if (value == "invalid-imbalance") + return mode::invalid_imbalance; + if (value == "mismatched-distribution") + return mode::mismatched_distribution; + if (value == "invalid-offsets") + return mode::invalid_offsets; + if (value == "missing-adjacency") + return mode::missing_adjacency; + if (value == "invalid-neighbor") + return mode::invalid_neighbor; + if (value == "mismatched-vertex-weights") { + return mode::mismatched_vertex_weights; + } + if (value == "missing-partition") + return mode::missing_partition; + if (value == "invalid-mode") + return mode::invalid_mode; + if (value == "global-weight-overflow") + return mode::global_weight_overflow; + if (value == "imbalanced-result") + return mode::imbalanced_result; + if (value == "intercommunicator") + return mode::intercommunicator; + parhip_interface_failure_probe::write_text( + "unknown ParHIP interface probe mode\n"); + std::_Exit(2); +} + +[[nodiscard]] auto make_intercommunicator(int world_rank) -> MPI_Comm { + auto local = MPI_COMM_NULL; + if (PMPI_Comm_split(MPI_COMM_WORLD, world_rank, 0, &local) != MPI_SUCCESS || + local == MPI_COMM_NULL) { + std::_Exit(7); + } + auto intercommunicator = MPI_COMM_NULL; + if (PMPI_Intercomm_create(local, 0, MPI_COMM_WORLD, 1 - world_rank, 947, + &intercommunicator) != MPI_SUCCESS || + intercommunicator == MPI_COMM_NULL) { + std::_Exit(8); + } + if (PMPI_Comm_free(&local) != MPI_SUCCESS) { + std::_Exit(9); + } + return intercommunicator; +} +} // namespace + +int main(int argc, char** argv) { + if (argc != 2 || MPI_Init(&argc, &argv) != MPI_SUCCESS) { + return 2; + } + auto world_rank = -1; + auto world_size = 0; + if (MPI_Comm_rank(MPI_COMM_WORLD, &world_rank) != MPI_SUCCESS || + MPI_Comm_size(MPI_COMM_WORLD, &world_size) != MPI_SUCCESS || + world_size != 2) { + return 3; + } + + using mode = parhip_interface_failure_probe::mode; + auto const selected = parse_mode(argv[1]); + auto communicator = MPI_COMM_NULL; + if (selected == mode::intercommunicator) { + communicator = make_intercommunicator(world_rank); + } else if (MPI_Comm_split(MPI_COMM_WORLD, 0, world_size - world_rank, + &communicator) != MPI_SUCCESS || + communicator == MPI_COMM_NULL) { + return 4; + } + if (MPI_Comm_set_errhandler(communicator, MPI_ERRORS_RETURN) != MPI_SUCCESS) { + return 5; + } + + auto rank = -1; + auto size = 0; + if (selected != mode::intercommunicator && + (MPI_Comm_rank(communicator, &rank) != MPI_SUCCESS || + MPI_Comm_size(communicator, &size) != MPI_SUCCESS || size != 2)) { + return 6; + } + + auto distribution = std::array{0, 2, 4}; + auto offsets = std::array{0, 2, 4}; + auto neighbors = rank == 0 ? std::array{1, 3, 0, 2} + : std::array{1, 3, 0, 2}; + auto vertex_weights = std::array{1, 1}; + auto partition = std::array{}; + auto blocks = 2; + auto imbalance = 0.03; + auto edge_cut = -1; + + if (selected == mode::global_weight_overflow) { + distribution = {0, 1, 2}; + offsets = {0, 0, 0}; + vertex_weights = { + std::numeric_limits::max() / 2 + 1, + std::numeric_limits::max() / 2 + 1, + }; + blocks = 1; + } + + if (selected == mode::mismatched_k) + blocks = rank + 2; + if (selected == mode::zero_k) + blocks = 0; + if (selected == mode::invalid_imbalance) { + imbalance = std::numeric_limits::infinity(); + } + if (selected == mode::mismatched_distribution && rank == 1) { + distribution[1] = 1; + } + if (selected == mode::invalid_offsets && rank == 0) { + offsets = {0, 3, 2}; + } + if (selected == mode::invalid_neighbor && rank == 0) { + neighbors[0] = 4; + } + + auto* distribution_pointer = distribution.data(); + auto* adjacency_pointer = neighbors.data(); + auto* vertex_weight_pointer = static_cast(nullptr); + auto* partition_pointer = partition.data(); + if (selected == mode::null_distribution && rank == 0) { + distribution_pointer = nullptr; + } + if (selected == mode::missing_adjacency && rank == 0) { + adjacency_pointer = nullptr; + } + if (selected == mode::mismatched_vertex_weights && rank == 0) { + vertex_weight_pointer = vertex_weights.data(); + } + if (selected == mode::missing_partition && rank == 0) { + partition_pointer = nullptr; + } + if (selected == mode::global_weight_overflow) { + adjacency_pointer = nullptr; + vertex_weight_pointer = vertex_weights.data(); + } + if (selected == mode::imbalanced_result) { + // Make the requested two-block balance mathematically infeasible: the + // weight-10 vertex exceeds the exact upper bound for total weight 13, no + // matter which block the partitioner selects. The postcondition check + // must therefore reject every possible partition deterministically. + if (rank == 0) { + vertex_weights.front() = 10; + } + vertex_weight_pointer = vertex_weights.data(); + } + + parhip_interface_failure_probe::selected = selected; + parhip_interface_failure_probe::caller_communicator = communicator; + parhip_interface_failure_probe::operation_rank = rank; + if (selected == mode::imbalanced_result) { + static_cast(kahip::diagnostics::exchange_sink_for_testing( + &parhip_interface_failure_probe::detail_sink)); + } + parhip_interface_failure_probe::active = true; + auto const partition_mode = selected == mode::invalid_mode ? 947 : FASTMESH; + ParHIPPartitionKWay(distribution_pointer, offsets.data(), adjacency_pointer, + vertex_weight_pointer, nullptr, &blocks, &imbalance, true, + 1, partition_mode, &edge_cut, partition_pointer, + &communicator); + + parhip_interface_failure_probe::write_text( + "ParHIP interface operation returned without fail-fast\n"); + std::_Exit(92); +} diff --git a/parallel/parallel_src/tests/interface/parhip_interface_mpi_test.cpp b/parallel/parallel_src/tests/interface/parhip_interface_mpi_test.cpp new file mode 100644 index 00000000..1b4335fa --- /dev/null +++ b/parallel/parallel_src/tests/interface/parhip_interface_mpi_test.cpp @@ -0,0 +1,350 @@ +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_handles.h" +#include "communication/serial_kernel_profile_observer.h" +#include "configuration.h" +#include "parhip_interface.h" + +namespace { +[[nodiscard]] auto reversed_world() -> MPI_Comm { + auto world_rank = -1; + auto world_size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &world_rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &world_size) == MPI_SUCCESS); + REQUIRE(world_size >= 1); + REQUIRE(world_size <= 5); + + auto communicator = MPI_COMM_NULL; + REQUIRE(MPI_Comm_split(MPI_COMM_WORLD, 0, world_size - world_rank, + &communicator) == MPI_SUCCESS); + REQUIRE(communicator != MPI_COMM_NULL); + return communicator; +} + +struct local_cycle final { + std::vector distribution; + std::vector offsets; + std::vector neighbors; + std::vector partition; +}; + +struct profile_capture final { + std::array profiles{}; + std::size_t count{}; + bool overflow{}; +}; + +void capture_profile( + void* context, + kahip::serial_kernel::serial_kernel_profile const& profile) noexcept { + auto& capture = *static_cast(context); + auto const index = capture.count++; + if (index < capture.profiles.size()) { + capture.profiles[index] = profile; + } else { + capture.overflow = true; + } +} + +[[nodiscard]] auto profile_fields( + kahip::serial_kernel::serial_kernel_profile const& profile) + -> std::array { + return {profile.global_nodes, + profile.global_directed_edges, + profile.total_node_weight, + profile.maximum_node_weight, + profile.total_directed_edge_weight, + profile.maximum_directed_edge_weight, + profile.block_count, + profile.absolute_bound, + profile.wire_record_bytes, + profile.csr_bytes, + profile.partition_bytes, + profile.serial_input_bytes, + profile.complete_graph_bytes, + profile.structural_validation_bytes, + profile.base_memory_bytes, + profile.flat_payload_elements, + static_cast(profile.reason)}; +} + +[[nodiscard]] auto make_cycle(int rank, int size, idxtype vertex_count) + -> local_cycle { + auto fixture = local_cycle{}; + fixture.distribution.resize(static_cast(size) + 1); + for (auto index = 0; index <= size; ++index) { + fixture.distribution[static_cast(index)] = + static_cast(index) * vertex_count / static_cast(size); + } + + auto const first = fixture.distribution[static_cast(rank)]; + auto const last = fixture.distribution[static_cast(rank) + 1]; + auto const local_count = last - first; + fixture.offsets.reserve(static_cast(local_count) + 1); + fixture.offsets.push_back(0); + fixture.neighbors.reserve(static_cast(2 * local_count)); + for (auto global = first; global < last; ++global) { + fixture.neighbors.push_back((global + vertex_count - 1) % vertex_count); + fixture.neighbors.push_back((global + 1) % vertex_count); + fixture.offsets.push_back(static_cast(fixture.neighbors.size())); + } + fixture.partition.resize(static_cast(local_count)); + return fixture; +} + +[[nodiscard]] auto require_valid_cycle_partition(local_cycle const& fixture, + int rank, + int size, + int edge_cut, + MPI_Comm communicator) + -> std::vector { + auto const vertex_count = + static_cast(fixture.distribution.back()); + constexpr auto block_count = idxtype{2}; + REQUIRE(std::ranges::all_of( + fixture.partition, [](idxtype block) { return block < block_count; })); + + auto counts = std::vector(static_cast(size)); + auto displacements = std::vector(static_cast(size)); + for (auto index = 0; index < size; ++index) { + auto const begin = fixture.distribution[static_cast(index)]; + auto const end = fixture.distribution[static_cast(index) + 1]; + REQUIRE(std::in_range(end - begin)); + REQUIRE(std::in_range(begin)); + counts[static_cast(index)] = static_cast(end - begin); + displacements[static_cast(index)] = static_cast(begin); + } + auto global_partition = std::vector(vertex_count); + auto const local_count = counts[static_cast(rank)]; + REQUIRE(MPI_Allgatherv(fixture.partition.data(), local_count, + MPI_UNSIGNED_LONG_LONG, global_partition.data(), + counts.data(), displacements.data(), + MPI_UNSIGNED_LONG_LONG, communicator) == MPI_SUCCESS); + + auto block_weights = std::array{}; + for (auto const block : global_partition) { + REQUIRE(block < block_count); + ++block_weights[static_cast(block)]; + } + auto const upper_bound = static_cast(vertex_count / 2); + REQUIRE(block_weights[0] <= upper_bound); + REQUIRE(block_weights[1] <= upper_bound); + + auto recomputed_cut = 0; + for (auto vertex = std::size_t{0}; vertex < vertex_count; ++vertex) { + auto const successor = (vertex + 1) % vertex_count; + recomputed_cut += + global_partition[vertex] != global_partition[successor] ? 1 : 0; + } + REQUIRE(edge_cut == recomputed_cut); + return global_partition; +} +} // namespace + +TEST_CASE("ParHIP ECO configuration follows the operation communicator") { + auto world_rank = -1; + auto world_size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &world_rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &world_size) == MPI_SUCCESS); + + auto subset = MPI_COMM_NULL; + REQUIRE(MPI_Comm_split(MPI_COMM_WORLD, world_rank % 2, world_rank, &subset) == + MPI_SUCCESS); + auto subset_size = 0; + REQUIRE(MPI_Comm_size(subset, &subset_size) == MPI_SUCCESS); + + auto config = parhip::PPartitionConfig{}; + auto defaults = parhip::configuration{}; + defaults.standard(config); + defaults.eco(config, parhip::mpi::communicator_view{subset}); + CHECK(config.evolutionary_time_limit == 2048 / subset_size); + if (subset_size != world_size) { + CHECK(config.evolutionary_time_limit != 2048 / world_size); + } + REQUIRE(MPI_Comm_free(&subset) == MPI_SUCCESS); +} + +TEST_CASE("ParHIP partitions a cycle on ranks one through five") { + auto communicator = reversed_world(); + auto rank = -1; + auto size = 0; + REQUIRE(MPI_Comm_rank(communicator, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(communicator, &size) == MPI_SUCCESS); + + auto fixture = make_cycle(rank, size, 32); + + auto blocks = 2; + auto imbalance = 0.03; + auto edge_cut = -1; + ParHIPPartitionKWay(fixture.distribution.data(), fixture.offsets.data(), + fixture.neighbors.data(), nullptr, nullptr, &blocks, + &imbalance, true, 1, FASTMESH, &edge_cut, + fixture.partition.data(), &communicator); + + auto const first_local_partition = fixture.partition; + auto const first_global_partition = require_valid_cycle_partition( + fixture, rank, size, edge_cut, communicator); + auto const first_edge_cut = edge_cut; + + std::ranges::fill(fixture.partition, std::numeric_limits::max()); + edge_cut = -1; + ParHIPPartitionKWay(fixture.distribution.data(), fixture.offsets.data(), + fixture.neighbors.data(), nullptr, nullptr, &blocks, + &imbalance, true, 1, FASTMESH, &edge_cut, + fixture.partition.data(), &communicator); + + auto const second_global_partition = require_valid_cycle_partition( + fixture, rank, size, edge_cut, communicator); + REQUIRE(fixture.partition == first_local_partition); + REQUIRE(second_global_partition == first_global_partition); + REQUIRE(edge_cut == first_edge_cut); + REQUIRE(MPI_Comm_free(&communicator) == MPI_SUCCESS); +} + +TEST_CASE("ParHIP does not coarsen a feasible block into an overweight cluster") { + auto communicator = reversed_world(); + auto rank = -1; + auto size = 0; + REQUIRE(MPI_Comm_rank(communicator, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(communicator, &size) == MPI_SUCCESS); + + auto fixture = make_cycle(rank, size, 4); + auto blocks = 2; + auto imbalance = 0.03; + auto edge_cut = -1; + + ParHIPPartitionKWay(fixture.distribution.data(), fixture.offsets.data(), + fixture.neighbors.data(), nullptr, nullptr, &blocks, + &imbalance, true, 1, FASTMESH, &edge_cut, + fixture.partition.data(), &communicator); + + static_cast(require_valid_cycle_partition( + fixture, rank, size, edge_cut, communicator)); + REQUIRE(MPI_Comm_free(&communicator) == MPI_SUCCESS); +} + +TEST_CASE("ParHIP preserves a binary32-origin three-percent weighted bound") { + auto communicator = reversed_world(); + auto rank = -1; + auto size = 0; + REQUIRE(MPI_Comm_rank(communicator, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(communicator, &size) == MPI_SUCCESS); + + auto fixture = make_cycle(rank, size, 2); + auto const first = fixture.distribution[static_cast(rank)]; + auto vertex_weights = + std::vector(std::max(std::size_t{1}, fixture.partition.size())); + for (auto local = std::size_t{0}; local < fixture.partition.size(); ++local) { + vertex_weights[local] = + first + static_cast(local) == 0 ? 35 : 33; + } + if (size > 2) { + auto local_has_no_work = fixture.partition.empty() ? 1 : 0; + auto has_zero_work_rank = 0; + REQUIRE(MPI_Allreduce(&local_has_no_work, &has_zero_work_rank, 1, MPI_INT, + MPI_MAX, communicator) == MPI_SUCCESS); + REQUIRE(has_zero_work_rank == 1); + } + + auto blocks = 2; + auto imbalance = static_cast(float{0.03F}); + auto edge_cut = -1; + ParHIPPartitionKWay(fixture.distribution.data(), fixture.offsets.data(), + fixture.neighbors.data(), vertex_weights.data(), nullptr, + &blocks, &imbalance, true, 1, FASTMESH, &edge_cut, + fixture.partition.data(), &communicator); + + auto local_block_weights = std::array{}; + for (auto local = std::size_t{0}; local < fixture.partition.size(); ++local) { + auto const block = fixture.partition[local]; + REQUIRE(block < static_cast(blocks)); + local_block_weights[static_cast(block)] += vertex_weights[local]; + } + auto global_block_weights = std::array{}; + REQUIRE(MPI_Allreduce(local_block_weights.data(), global_block_weights.data(), + static_cast(global_block_weights.size()), + MPI_UNSIGNED_LONG_LONG, MPI_SUM, communicator) == + MPI_SUCCESS); + REQUIRE(*std::ranges::max_element(global_block_weights) == 35); + REQUIRE(MPI_Comm_free(&communicator) == MPI_SUCCESS); +} + +TEST_CASE("ParHIP accepts a leading zero-work rank") { + auto communicator = reversed_world(); + auto rank = -1; + auto size = 0; + REQUIRE(MPI_Comm_rank(communicator, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(communicator, &size) == MPI_SUCCESS); + if (size != 5) { + REQUIRE(MPI_Comm_free(&communicator) == MPI_SUCCESS); + return; + } + + auto fixture = make_cycle(rank, size, 4); + if (rank == 0) { + REQUIRE(fixture.partition.empty()); + REQUIRE(fixture.neighbors.empty()); + } + auto blocks = 1; + auto imbalance = 0.03; + auto edge_cut = -1; + ParHIPPartitionKWay(fixture.distribution.data(), fixture.offsets.data(), + fixture.neighbors.data(), nullptr, nullptr, &blocks, + &imbalance, true, 1, FASTMESH, &edge_cut, + fixture.partition.data(), &communicator); + + REQUIRE(std::ranges::all_of(fixture.partition, + [](idxtype block) { return block == 0; })); + REQUIRE(edge_cut == 0); + REQUIRE(MPI_Comm_free(&communicator) == MPI_SUCCESS); +} + +TEST_CASE("ParHIP FASTSOCIAL C call observes each checked quotient once") { + auto communicator = reversed_world(); + auto rank = -1; + auto size = 0; + REQUIRE(MPI_Comm_rank(communicator, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(communicator, &size) == MPI_SUCCESS); + + auto distribution = std::vector(static_cast(size) + 1); + for (auto index = 0; index <= size; ++index) { + distribution[static_cast(index)] = index; + } + auto offsets = std::array{0, 0}; + auto partition = std::array{}; + auto blocks = 1; + auto imbalance = 0.0; + auto edge_cut = -1; + auto capture = profile_capture{}; + auto observer = parhip::mpi_tools_detail::scoped_serial_kernel_profile_observer{ + capture_profile, &capture}; + + ParHIPPartitionKWay(distribution.data(), offsets.data(), nullptr, nullptr, + nullptr, &blocks, &imbalance, true, 19, FASTSOCIAL, + &edge_cut, partition.data(), &communicator); + + REQUIRE(capture.count == 2); + CHECK_FALSE(capture.overflow); + auto const nodes = static_cast(size); + auto const expected = std::array{ + nodes, 0, nodes, 1, 0, 0, 1, nodes, 32 * nodes, 8 * nodes + 4, + 4 * nodes, 12 * nodes + 4, 40 * nodes + 40, 0, + 72 * nodes + 40, 2 * nodes + 1, + static_cast( + kahip::serial_kernel::profile_reason::none)}; + CHECK(profile_fields(capture.profiles[0]) == expected); + CHECK(profile_fields(capture.profiles[1]) == expected); + CHECK(partition[0] == 0); + CHECK(edge_cut == 0); + REQUIRE(MPI_Comm_free(&communicator) == MPI_SUCCESS); +} diff --git a/parallel/parallel_src/tests/interface/parhip_partition_balance_test.cpp b/parallel/parallel_src/tests/interface/parhip_partition_balance_test.cpp new file mode 100644 index 00000000..5867100a --- /dev/null +++ b/parallel/parallel_src/tests/interface/parhip_partition_balance_test.cpp @@ -0,0 +1,139 @@ +#include +#include +#include +#include + +#include + +#include "imbalance.h" +#include "interface/parhip_partition_balance.h" +#include "random_state.h" + +TEST_CASE("native three-percent imbalance keeps its whole percentage", + "[parhip][partition][balance]") { + auto const normalized = kahip::balance::normalize_fractional_imbalance(0.03); + + REQUIRE(normalized.has_value()); + CHECK(normalized->effective_percent == 3); + CHECK_FALSE(normalized->was_normalized); +} + +TEST_CASE("only the exact widened binary32 origin normalizes upward", + "[parhip][partition][balance]") { + auto const widened = + kahip::balance::normalize_fractional_imbalance( + static_cast(float{0.03F})); + auto const preceding_binary32 = + kahip::balance::normalize_fractional_imbalance(static_cast( + std::nextafter(float{0.03F}, 0.0F))); + auto const genuine_near_integer = + kahip::balance::normalize_fractional_imbalance(0.029999998); + auto const large_percentage_ambiguity = + kahip::balance::normalize_fractional_imbalance(83886.075); + + REQUIRE(widened.has_value()); + CHECK(widened->effective_percent == 3); + CHECK(widened->was_normalized); + REQUIRE(preceding_binary32.has_value()); + CHECK(preceding_binary32->effective_percent == 2); + CHECK_FALSE(preceding_binary32->was_normalized); + REQUIRE(genuine_near_integer.has_value()); + CHECK(genuine_near_integer->effective_percent == 2); + CHECK_FALSE(genuine_near_integer->was_normalized); + REQUIRE(large_percentage_ambiguity.has_value()); + CHECK(large_percentage_ambiguity->effective_percent == 8388607); + CHECK_FALSE(large_percentage_ambiguity->was_normalized); +} + +TEST_CASE("colliding widened binary32 origins retain historical floor semantics", + "[parhip][partition][balance]") { + auto const collision = static_cast( + static_cast(16000002.0 / 100.0)); + auto const collision_lower_target = static_cast( + static_cast(16000001.0 / 100.0)); + auto const normalized = + kahip::balance::normalize_fractional_imbalance(collision); + auto const minimum = kahip::balance::normalize_fractional_imbalance( + static_cast(float{0.01F})); + + REQUIRE(collision == collision_lower_target); + REQUIRE(normalized.has_value()); + CHECK(normalized->effective_percent == 16000001U); + CHECK_FALSE(normalized->was_normalized); + REQUIRE(minimum.has_value()); + CHECK(minimum->effective_percent == 1U); + CHECK(minimum->was_normalized); +} + +TEST_CASE("genuine fractional percentages retain floor semantics", + "[parhip][partition][balance]") { + auto const normalized = + kahip::balance::normalize_fractional_imbalance(0.025); + + REQUIRE(normalized.has_value()); + CHECK(normalized->effective_percent == 2); + CHECK_FALSE(normalized->was_normalized); +} + +TEST_CASE("imbalance normalization rejects invalid and out-of-range inputs", + "[parhip][partition][balance]") { + auto const largest_fraction = + static_cast(std::numeric_limits::max()) / 100.0; + auto const largest = + kahip::balance::normalize_fractional_imbalance(largest_fraction); + + REQUIRE(largest.has_value()); + CHECK(largest->effective_percent == std::numeric_limits::max()); + CHECK_FALSE(largest->was_normalized); + CHECK_FALSE(kahip::balance::normalize_fractional_imbalance(-0.01)); + CHECK_FALSE(kahip::balance::normalize_fractional_imbalance( + std::numeric_limits::infinity())); + CHECK_FALSE(kahip::balance::normalize_fractional_imbalance( + std::numeric_limits::quiet_NaN())); + CHECK_FALSE(kahip::balance::normalize_fractional_imbalance( + std::nextafter(largest_fraction, std::numeric_limits::infinity()))); +} + +TEST_CASE("normalized three percent keeps the checked 600-cubed bound", + "[parhip][partition][balance]") { + auto const normalized = kahip::balance::normalize_fractional_imbalance(0.03); + + REQUIRE(normalized.has_value()); + CHECK(kahip::random_compat::exact_partition_upper_bound( + 600ULL * 600ULL * 600ULL, 2304ULL, + normalized->effective_percent) == 96562ULL); + CHECK_FALSE(kahip::random_compat::exact_partition_upper_bound( + std::numeric_limits::max(), 1ULL, 1U)); +} + +TEST_CASE("lowest-ID heaviest block wins ties", "[parhip][partition][balance]") { + auto const weights = std::array{10, 10, 3, 3}; + + auto const [block, weight] = + parhip::detail::lowest_id_heaviest_block(std::span{weights}); + + CHECK(block == 0); + CHECK(weight == 10); +} + +TEST_CASE("lowest-ID heaviest block keeps the first nonzero tie", + "[parhip][partition][balance]") { + auto const weights = std::array{3, 10, 10}; + + auto const [block, weight] = + parhip::detail::lowest_id_heaviest_block(std::span{weights}); + + CHECK(block == 1); + CHECK(weight == 10); +} + +TEST_CASE("lowest-ID heaviest block returns a unique maximum", + "[parhip][partition][balance]") { + auto const weights = std::array{3, 7, 11, 2}; + + auto const [block, weight] = + parhip::detail::lowest_id_heaviest_block(std::span{weights}); + + CHECK(block == 2); + CHECK(weight == 11); +} diff --git a/parallel/parallel_src/tests/interface/verify_parhip_interface_determinism.cmake b/parallel/parallel_src/tests/interface/verify_parhip_interface_determinism.cmake new file mode 100644 index 00000000..cfec73dc --- /dev/null +++ b/parallel/parallel_src/tests/interface/verify_parhip_interface_determinism.cmake @@ -0,0 +1,93 @@ +cmake_minimum_required(VERSION 4.0) + +if( + NOT DEFINED MPIEXEC_EXECUTABLE + OR NOT DEFINED MPIEXEC_NUMPROC_FLAG + OR NOT DEFINED PROBE +) + message(FATAL_ERROR "MPI launcher and PROBE are required") +endif() + +foreach(run_mode IN ITEMS fresh contaminated wrapped) + execute_process( + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 2 + ${MPIEXEC_PREFLAGS} + "${PROBE}" + ${MPIEXEC_POSTFLAGS} + "${run_mode}" + RESULT_VARIABLE ${run_mode}_result + OUTPUT_VARIABLE ${run_mode}_stdout + ERROR_VARIABLE ${run_mode}_stderr + TIMEOUT 60 + ) + if(NOT "${${run_mode}_result}" STREQUAL "0") + message( + FATAL_ERROR + "${run_mode} determinism probe failed: ${${run_mode}_result}\n${${run_mode}_stdout}\n${${run_mode}_stderr}" + ) + endif() + string( + REGEX MATCHALL + "PARHIP_TARGET [0-9 ]+" + ${run_mode}_targets + "${${run_mode}_stdout}" + ) +endforeach() + +list(LENGTH fresh_targets fresh_count) +list(LENGTH contaminated_targets contaminated_count) +list(LENGTH wrapped_targets wrapped_count) +if(NOT fresh_count EQUAL 1) + message( + FATAL_ERROR + "expected one fresh target result; found ${fresh_count}\n${fresh_stdout}" + ) +endif() +if(NOT contaminated_count EQUAL 2) + message( + FATAL_ERROR + "expected two repeated target results; found ${contaminated_count}\n${contaminated_stdout}" + ) +endif() +if(NOT wrapped_count EQUAL 2) + message( + FATAL_ERROR + "expected two wrapped-seed target results; found ${wrapped_count}\n${wrapped_stdout}" + ) +endif() +list(GET fresh_targets 0 fresh_target) +list(GET contaminated_targets 0 first_contaminated_target) +list(GET contaminated_targets 1 second_contaminated_target) +list(GET wrapped_targets 0 first_wrapped_target) +list(GET wrapped_targets 1 second_wrapped_target) +if(NOT first_contaminated_target STREQUAL second_contaminated_target) + message( + FATAL_ERROR + "same-process repeated calls diverged\nfresh=${fresh_target}\nfirst=${first_contaminated_target}\nsecond=${second_contaminated_target}" + ) +endif() +if(NOT fresh_target STREQUAL first_contaminated_target) + message( + FATAL_ERROR + "prior calls changed deterministic target semantics\nfresh=${fresh_target}\ncontaminated=${first_contaminated_target}" + ) +endif() +if(NOT first_wrapped_target STREQUAL second_wrapped_target) + message( + FATAL_ERROR + "same-process wrapped-seed calls diverged\nfirst=${first_wrapped_target}\nsecond=${second_wrapped_target}" + ) +endif() + +string(SHA256 wrapped_target_sha256 "${first_wrapped_target}") +set( + expected_wrapped_target_sha256 + "6f4e493d31b96144dabfd50ba90ed99b78d304c6f75b5e8cffec30fef11f0d95" +) +if(NOT wrapped_target_sha256 STREQUAL expected_wrapped_target_sha256) + message( + FATAL_ERROR + "wrapped-seed target changed: expected ${expected_wrapped_target_sha256}, got ${wrapped_target_sha256}\n${first_wrapped_target}" + ) +endif() diff --git a/parallel/parallel_src/tests/interface/verify_parhip_interface_failure.cmake b/parallel/parallel_src/tests/interface/verify_parhip_interface_failure.cmake new file mode 100644 index 00000000..fd033da6 --- /dev/null +++ b/parallel/parallel_src/tests/interface/verify_parhip_interface_failure.cmake @@ -0,0 +1,105 @@ +cmake_minimum_required(VERSION 4.0) + +if( + NOT DEFINED MPIEXEC_EXECUTABLE + OR NOT DEFINED MPIEXEC_NUMPROC_FLAG + OR NOT DEFINED PROBE + OR NOT DEFINED MODE + OR NOT DEFINED EXPECTED_DIAGNOSTIC +) + message( + FATAL_ERROR + "MPI launcher, PROBE, MODE, and EXPECTED_DIAGNOSTIC are required" + ) +endif() + +execute_process( + COMMAND + "${MPIEXEC_EXECUTABLE}" "${MPIEXEC_NUMPROC_FLAG}" 2 + ${MPIEXEC_PREFLAGS} + "${PROBE}" + ${MPIEXEC_POSTFLAGS} + "${MODE}" + RESULT_VARIABLE probe_result + OUTPUT_VARIABLE probe_stdout + ERROR_VARIABLE probe_stderr + TIMEOUT 20 +) + +set(probe_output "${probe_stdout}\n${probe_stderr}") +if("${probe_result}" STREQUAL "0") + message(FATAL_ERROR "failure probe returned success\n${probe_output}") +endif() +if("${probe_result}" MATCHES "[Tt]imeout") + message(FATAL_ERROR "failure probe timed out\n${probe_output}") +endif() + +string( + REGEX MATCHALL + "observed ParHIP interface MPI_Abort on affected communicator" + abort_markers + "${probe_output}" +) +list(LENGTH abort_markers abort_count) +if(NOT abort_count EQUAL 2) + message( + FATAL_ERROR + "expected exactly 2 affected-communicator abort markers; found ${abort_count}\n${probe_output}" + ) +endif() + +string(FIND "${probe_output}" "${EXPECTED_DIAGNOSTIC}" diagnostic_offset) +if(diagnostic_offset EQUAL -1) + message( + FATAL_ERROR + "missing ParHIP interface failure diagnostic '${EXPECTED_DIAGNOSTIC}'\n${probe_output}" + ) +endif() + +if(MODE STREQUAL "imbalanced-result") + string( + REGEX MATCHALL + "PARHIP_DETAIL rank=0 ParHIP partition balance failure: raw imbalance=0\\.029999999999999999, effective percentage=3%, normalization status=false, total weight=13, block count=2, configured bound=7, lowest-ID heaviest block=0, actual weight=10, excess=3" + detail_lines + "${probe_output}" + ) + list(LENGTH detail_lines detail_count) + if(NOT detail_count EQUAL 1) + message( + FATAL_ERROR + "expected exactly one complete rank-zero ParHIP balance detail; found ${detail_count}\n${probe_output}" + ) + endif() + string(REGEX MATCHALL "PARHIP_DETAIL rank=0 " rank_zero_details "${probe_output}") + list(LENGTH rank_zero_details rank_zero_detail_count) + if(NOT rank_zero_detail_count EQUAL 1) + message(FATAL_ERROR "expected exactly one rank-zero detail\n${probe_output}") + endif() + string(REGEX MATCHALL "PARHIP_DETAIL rank=1 " rank_one_details "${probe_output}") + list(LENGTH rank_one_details rank_one_detail_count) + if(NOT rank_one_detail_count EQUAL 0) + message(FATAL_ERROR "unexpected rank-one detail\n${probe_output}") + endif() + string(REGEX MATCHALL "PARHIP_DETAIL_FLUSH rank=0" rank_zero_flushes "${probe_output}") + list(LENGTH rank_zero_flushes rank_zero_flush_count) + if(NOT rank_zero_flush_count EQUAL 1) + message(FATAL_ERROR "expected exactly one rank-zero detail flush\n${probe_output}") + endif() + string(FIND "${probe_output}" "PARHIP_DETAIL_FLUSH rank=1" rank_one_flush) + if(NOT rank_one_flush EQUAL -1) + message(FATAL_ERROR "unexpected rank-one detail flush\n${probe_output}") + endif() +endif() + +foreach( + forbidden + IN ITEMS + "unexpected state" + "returned without fail-fast" + "MPI_Finalize" +) + string(FIND "${probe_output}" "${forbidden}" forbidden_offset) + if(NOT forbidden_offset EQUAL -1) + message(FATAL_ERROR "failure probe used ${forbidden}\n${probe_output}") + endif() +endforeach() diff --git a/parallel/parallel_src/tests/io/edge_balanced_graph_io_failure_probe.cpp b/parallel/parallel_src/tests/io/edge_balanced_graph_io_failure_probe.cpp new file mode 100644 index 00000000..3f35bbc1 --- /dev/null +++ b/parallel/parallel_src/tests/io/edge_balanced_graph_io_failure_probe.cpp @@ -0,0 +1,215 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "communication/mpi_handles.h" +#include "data_structure/parallel_graph_access.h" +#include "dspac/edge_balanced_graph_io.h" +#include "partition_config.h" + +// KAHIP_PMPI_CALLBACK_REGION_BEGIN +namespace edge_balanced_failure_probe { +inline auto active = false; +inline auto expected_communicator = MPI_COMM_NULL; +inline auto communicator_rank = -1; +inline auto finalizations = 0; +inline auto fixture = std::array{}; +inline auto marker = std::array{}; +inline auto marker_size = std::size_t{0}; + +void write_text(std::string_view text) noexcept { + static_cast(::write(STDERR_FILENO, text.data(), text.size())); +} + +[[noreturn]] void observed_abort(MPI_Comm communicator, + int error_code) noexcept { + auto relation = int{MPI_UNEQUAL}; + if (error_code != EXIT_FAILURE || finalizations != 0 || + communicator_rank < 0 || communicator_rank > 1 || + PMPI_Comm_compare(communicator, expected_communicator, &relation) != + MPI_SUCCESS || + (relation != MPI_IDENT && relation != MPI_CONGRUENT)) { + write_text("observed edge-balanced MPI_Abort with unexpected state\n"); + std::_Exit(91); + } + write_text(std::string_view{marker.data(), marker_size}); + if (PMPI_Barrier(MPI_COMM_WORLD) != MPI_SUCCESS) { + std::_Exit(90); + } + if (communicator_rank == 0) { + static_cast(::unlink(fixture.data())); + } + std::_Exit(86); +} +} // namespace edge_balanced_failure_probe + +static_assert(noexcept(edge_balanced_failure_probe::write_text({}))); +static_assert( + noexcept(edge_balanced_failure_probe::observed_abort(MPI_COMM_NULL, 0))); + +extern "C" int MPI_Finalize() { + if (edge_balanced_failure_probe::active) { + ++edge_balanced_failure_probe::finalizations; + return MPI_SUCCESS; + } + return PMPI_Finalize(); +} + +extern "C" int MPI_Abort(MPI_Comm communicator, int error_code) { + edge_balanced_failure_probe::observed_abort(communicator, error_code); +} +// KAHIP_PMPI_CALLBACK_REGION_END + +namespace { +using parhip::ULONG; + +enum class failure_mode : unsigned char { + truncated, + nonmonotone, + unaligned, + wrong_terminal, + invalid_target, + zero_window, +}; + +[[nodiscard]] auto parse_mode(std::string_view value) -> failure_mode { + if (value == "truncated") { + return failure_mode::truncated; + } + if (value == "nonmonotone") { + return failure_mode::nonmonotone; + } + if (value == "unaligned") { + return failure_mode::unaligned; + } + if (value == "wrong-terminal") { + return failure_mode::wrong_terminal; + } + if (value == "invalid-target") { + return failure_mode::invalid_target; + } + if (value == "window-zero") { + return failure_mode::zero_window; + } + std::_Exit(2); +} + +void write_fixture(failure_mode mode, std::string const& filename) { + auto output = std::ofstream{filename, std::ios::binary | std::ios::trunc}; + auto const header = std::array{3, 3, 4}; + auto offsets = std::array{56, 72, 80, 88}; + auto adjacency = std::array{2, 1, 0, 1}; + switch (mode) { + case failure_mode::nonmonotone: + offsets = {56, 80, 72, 88}; + break; + case failure_mode::unaligned: + offsets = {56, 73, 80, 88}; + break; + case failure_mode::wrong_terminal: + offsets = {56, 72, 80, 80}; + break; + case failure_mode::invalid_target: + adjacency = {2, 1, 0, 3}; + break; + case failure_mode::truncated: + case failure_mode::zero_window: + break; + } + + output.write(reinterpret_cast(header.data()), + static_cast(sizeof(header))); + output.write(reinterpret_cast(offsets.data()), + static_cast(sizeof(offsets))); + auto const adjacency_words = + mode == failure_mode::truncated ? std::size_t{3} : adjacency.size(); + output.write(reinterpret_cast(adjacency.data()), + static_cast(adjacency_words * sizeof(ULONG))); + if (!output) { + std::_Exit(3); + } +} +} // namespace + +int main(int argc, char** argv) { + if (argc != 2 || MPI_Init(&argc, &argv) != MPI_SUCCESS) { + return 2; + } + auto world_rank = 0; + auto world_size = 0; + if (MPI_Comm_rank(MPI_COMM_WORLD, &world_rank) != MPI_SUCCESS || + MPI_Comm_size(MPI_COMM_WORLD, &world_size) != MPI_SUCCESS || + world_size != 2) { + return 3; + } + + auto communicator = MPI_COMM_NULL; + if (MPI_Comm_split(MPI_COMM_WORLD, 0, world_size - world_rank, + &communicator) != MPI_SUCCESS || + communicator == MPI_COMM_NULL || + MPI_Comm_set_errhandler(communicator, MPI_ERRORS_RETURN) != MPI_SUCCESS) { + return 4; + } + auto rank = -1; + if (MPI_Comm_rank(communicator, &rank) != MPI_SUCCESS) { + return 5; + } + + auto const selected = parse_mode(argv[1]); + auto filename = std::string{}; + if (rank == 0) { + filename = "/tmp/kahip-edge-balanced-failure-" + + std::to_string(::getpid()) + ".bgf"; + write_fixture(selected, filename); + } + auto filename_size = std::uint64_t{filename.size()}; + if (MPI_Bcast(&filename_size, 1, MPI_UINT64_T, 0, communicator) != + MPI_SUCCESS || + filename_size > + static_cast(std::numeric_limits::max())) { + return 6; + } + filename.resize(static_cast(filename_size)); + if (MPI_Bcast(filename.data(), static_cast(filename_size), MPI_CHAR, 0, + communicator) != MPI_SUCCESS || + MPI_Barrier(communicator) != MPI_SUCCESS) { + return 7; + } + + edge_balanced_failure_probe::expected_communicator = communicator; + edge_balanced_failure_probe::communicator_rank = rank; + if (filename.size() + 1 > edge_balanced_failure_probe::fixture.size()) { + return 8; + } + std::ranges::copy(filename, edge_balanced_failure_probe::fixture.begin()); + edge_balanced_failure_probe::fixture[filename.size()] = '\0'; + auto const marker = + "observed MPI_Abort rank=" + std::to_string(rank) + " edge-balanced-" + + std::string{argv[1]} + + " affected-communicator; internal MPI_Finalize counter is zero\n"; + if (marker.size() > edge_balanced_failure_probe::marker.size()) { + return 8; + } + std::ranges::copy(marker, edge_balanced_failure_probe::marker.begin()); + edge_balanced_failure_probe::marker_size = marker.size(); + + auto graph = parhip::parallel_graph_access{communicator}; + auto config = parhip::PPartitionConfig{}; + config.binary_io_window_size = selected == failure_mode::zero_window ? 0 : 1; + auto permutation = std::vector{}; + edge_balanced_failure_probe::active = true; + parhip::edge_balanced_graph_io::read_binary_graph_edge_balanced( + graph, filename, config, permutation, + parhip::mpi::communicator_view{communicator}); + edge_balanced_failure_probe::write_text("returned-from-failure\n"); + std::_Exit(92); +} diff --git a/parallel/parallel_src/tests/io/edge_balanced_graph_io_layout_test.cpp b/parallel/parallel_src/tests/io/edge_balanced_graph_io_layout_test.cpp new file mode 100644 index 00000000..c99ee0d3 --- /dev/null +++ b/parallel/parallel_src/tests/io/edge_balanced_graph_io_layout_test.cpp @@ -0,0 +1,109 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "dspac/edge_balanced_graph_io.h" + +namespace { +using parhip::EdgeID; +using parhip::NodeID; +using parhip::ULONG; +namespace detail = parhip::edge_balanced_graph_io_detail; + +void require(bool condition, std::string_view diagnostic) { + if (!condition) { + std::cerr << diagnostic << '\n'; + std::exit(EXIT_FAILURE); + } +} + +[[nodiscard]] auto checksum(std::span values) noexcept + -> std::uint64_t { + auto result = std::uint64_t{14695981039346656037ULL}; + for (auto value : values) { + result ^= static_cast(value); + result *= std::uint64_t{1099511628211ULL}; + } + return result; +} +} // namespace + +int main() { + auto const empty_layout = detail::make_binary_layout(0, 0); + require(empty_layout.has_value(), + "empty binary layout must be representable"); + require(empty_layout->adjacency_begin == ULONG{32} && + empty_layout->file_extent == ULONG{32}, + "empty binary layout must contain one terminal offset"); + auto const empty_offsets = std::array{32}; + require(detail::offsets_are_valid(empty_offsets, *empty_layout, true, true), + "empty graph terminal offset must be valid"); + + auto const edgeless_layout = detail::make_binary_layout(3, 0); + require(edgeless_layout.has_value(), + "edgeless binary layout must be representable"); + auto const edgeless_offsets = std::array{56, 56, 56, 56}; + require( + detail::offsets_are_valid(edgeless_offsets, *edgeless_layout, true, true), + "repeated offsets must be valid for isolated vertices"); + auto const edgeless_ranges = + detail::node_ranges_from_offsets(edgeless_offsets, 0, 5); + require( + edgeless_ranges == std::vector{NodeID{0}, NodeID{1}, NodeID{2}, + NodeID{3}, NodeID{3}, NodeID{3}}, + "edgeless graphs must use deterministic vertex-balanced ranges"); + + auto const small_layout = detail::make_binary_layout(3, 4); + require(small_layout.has_value(), "3-vertex layout must be representable"); + auto const small_offsets = std::array{56, 72, 80, 88}; + require(detail::offsets_are_valid(small_offsets, *small_layout, true, true), + "valid uneven offsets must be accepted"); + auto const small_ranges = + detail::node_ranges_from_offsets(small_offsets, 4, 5); + require(small_ranges == std::vector{NodeID{0}, NodeID{1}, NodeID{1}, + NodeID{2}, NodeID{3}, NodeID{3}}, + "edge-balanced lower-bound ranges must allow zero-work ranks"); + + require(!detail::validated_window(0, 5).has_value(), + "a zero I/O window must be rejected"); + require(!detail::validated_window(-1, 5).has_value(), + "a negative I/O window must be rejected"); + require(detail::validated_window(8, 5) == 5, + "the I/O window must not exceed communicator size"); + + require(!detail::file_extent_is_valid(87, *small_layout), + "a truncated payload must be rejected"); + require(!detail::file_extent_is_valid(96, *small_layout), + "trailing payload bytes must be rejected"); + auto const nonmonotone = std::array{56, 80, 72, 88}; + require(!detail::offsets_are_valid(nonmonotone, *small_layout, true, true), + "nonmonotone offsets must be rejected"); + auto const unaligned = std::array{56, 73, 80, 88}; + require(!detail::offsets_are_valid(unaligned, *small_layout, true, true), + "unaligned offsets must be rejected"); + auto const wrong_terminal = std::array{56, 72, 80, 80}; + require(!detail::offsets_are_valid(wrong_terminal, *small_layout, true, true), + "an incorrect terminal offset must be rejected"); + auto const invalid_target = std::array{3}; + require(!detail::targets_are_valid(invalid_target, 3), + "an out-of-domain target must be rejected"); + require(detail::targets_are_valid(std::span{}, 0), + "an empty graph must have a valid empty target range"); + + auto adjacency = std::vector{2, 0, 1, 2, 0}; + auto permutation = std::vector(adjacency.size()); + auto const local_offsets = std::array{64, 88, 104}; + require(detail::canonicalize_adjacency(local_offsets, adjacency, permutation), + "valid local adjacency must canonicalize"); + require(adjacency == std::vector{0, 1, 2, 0, 2}, + "adjacency targets must be sorted within each vertex"); + require(permutation == std::vector{1, 2, 0, 4, 3}, + "permutation must retain original local edge positions"); + require(checksum(adjacency) == std::uint64_t{3706092854568170190ULL} && + checksum(permutation) == std::uint64_t{1726334132918446519ULL}, + "valid fixture checksums must remain exact"); +} diff --git a/parallel/parallel_src/tests/io/edge_balanced_graph_io_mpi_test.cpp b/parallel/parallel_src/tests/io/edge_balanced_graph_io_mpi_test.cpp new file mode 100644 index 00000000..415b3189 --- /dev/null +++ b/parallel/parallel_src/tests/io/edge_balanced_graph_io_mpi_test.cpp @@ -0,0 +1,227 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "communication/mpi_handles.h" +#include "data_structure/parallel_graph_access.h" +#include "dspac/edge_balanced_graph_io.h" +#include "partition_config.h" + +namespace { +using parhip::EdgeID; +using parhip::NodeID; +using parhip::ULONG; + +[[nodiscard]] auto communicator_rank(MPI_Comm communicator) -> int { + auto rank = -1; + REQUIRE(MPI_Comm_rank(communicator, &rank) == MPI_SUCCESS); + return rank; +} + +[[nodiscard]] auto communicator_size(MPI_Comm communicator) -> int { + auto size = 0; + REQUIRE(MPI_Comm_size(communicator, &size) == MPI_SUCCESS); + return size; +} + +[[nodiscard]] auto shared_fixture_path(MPI_Comm communicator, + std::string_view suffix) + -> std::filesystem::path { + auto path = std::string{}; + if (communicator_rank(communicator) == 0) { + path = (std::filesystem::temp_directory_path() / + ("kahip-edge-balanced-" + std::to_string(::getpid()) + "-" + + std::string{suffix})) + .string(); + } + auto length = static_cast(path.size()); + REQUIRE(MPI_Bcast(&length, 1, MPI_UINT64_T, 0, communicator) == MPI_SUCCESS); + REQUIRE(length <= + static_cast(std::numeric_limits::max())); + path.resize(static_cast(length)); + REQUIRE(MPI_Bcast(path.data(), static_cast(length), MPI_CHAR, 0, + communicator) == MPI_SUCCESS); + return path; +} + +void write_binary_graph(MPI_Comm communicator, + std::filesystem::path const& path, + NodeID nodes, + EdgeID edges, + std::span offsets, + std::span adjacency) { + auto success = 1; + if (communicator_rank(communicator) == 0) { + auto output = std::ofstream{path, std::ios::binary | std::ios::trunc}; + auto const header = std::array{3, nodes, edges}; + output.write(reinterpret_cast(header.data()), + static_cast(sizeof(header))); + output.write(reinterpret_cast(offsets.data()), + static_cast(offsets.size_bytes())); + output.write(reinterpret_cast(adjacency.data()), + static_cast(adjacency.size_bytes())); + success = output ? 1 : 0; + } + REQUIRE(MPI_Bcast(&success, 1, MPI_INT, 0, communicator) == MPI_SUCCESS); + REQUIRE(success == 1); + REQUIRE(MPI_Barrier(communicator) == MPI_SUCCESS); +} + +void remove_fixture(MPI_Comm communicator, std::filesystem::path const& path) { + REQUIRE(MPI_Barrier(communicator) == MPI_SUCCESS); + if (communicator_rank(communicator) == 0) { + auto error = std::error_code{}; + static_cast(std::filesystem::remove(path, error)); + REQUIRE_FALSE(error); + } + REQUIRE(MPI_Barrier(communicator) == MPI_SUCCESS); +} + +void require_common(bool condition, MPI_Comm communicator) { + auto const local = condition ? 1 : 0; + auto global = 0; + REQUIRE(MPI_Allreduce(&local, &global, 1, MPI_INT, MPI_MIN, communicator) == + MPI_SUCCESS); + REQUIRE(global == 1); +} + +[[nodiscard]] auto local_targets(parhip::parallel_graph_access& graph) + -> std::vector { + auto result = std::vector{}; + for (auto local = NodeID{0}; local < graph.number_of_local_nodes(); ++local) { + for (auto edge = graph.get_first_edge(local); + edge < graph.get_first_invalid_edge(local); ++edge) { + result.push_back(graph.getGlobalID(graph.getEdgeTarget(edge))); + } + } + return result; +} +} // namespace + +TEST_CASE("edge-balanced binary input supports empty and edgeless graphs", + "[mpi][parallel-io][edge-balanced][zero-work]") { + auto communicator = MPI_COMM_NULL; + auto const world_rank = communicator_rank(MPI_COMM_WORLD); + auto const world_size = communicator_size(MPI_COMM_WORLD); + REQUIRE(world_size == 5); + REQUIRE(MPI_Comm_split(MPI_COMM_WORLD, 0, world_size - world_rank, + &communicator) == MPI_SUCCESS); + auto const rank = communicator_rank(communicator); + + auto config = parhip::PPartitionConfig{}; + config.binary_io_window_size = 2; + + auto const empty_path = shared_fixture_path(communicator, "empty.bgf"); + auto const empty_offsets = std::array{32}; + write_binary_graph(communicator, empty_path, 0, 0, empty_offsets, {}); + { + auto graph = parhip::parallel_graph_access{communicator}; + auto permutation = std::vector{17}; + parhip::edge_balanced_graph_io::read_binary_graph_edge_balanced( + graph, empty_path.string(), config, permutation, + parhip::mpi::communicator_view{communicator}); + require_common( + graph.number_of_local_nodes() == 0 && + graph.number_of_local_edges() == 0 && graph.get_from_range() == 0 && + graph.get_to_range() == 0 && permutation.empty() && + graph.get_range_array() == std::vector{0, 0, 0, 0, 0, 0} && + graph.get_edge_range_array() == + std::vector{0, 0, 0, 0, 0, 0}, + communicator); + } + remove_fixture(communicator, empty_path); + + auto const edgeless_path = shared_fixture_path(communicator, "edgeless.bgf"); + auto const edgeless_offsets = std::array{56, 56, 56, 56}; + write_binary_graph(communicator, edgeless_path, 3, 0, edgeless_offsets, {}); + { + auto graph = parhip::parallel_graph_access{communicator}; + auto permutation = std::vector{}; + parhip::edge_balanced_graph_io::read_binary_graph_edge_balanced( + graph, edgeless_path.string(), config, permutation, + parhip::mpi::communicator_view{communicator}); + auto const expected_ranges = std::vector{0, 1, 2, 3, 3, 3}; + auto const expected_nodes = rank < 3 ? NodeID{1} : NodeID{0}; + auto const expected_from = expected_ranges[static_cast(rank)]; + require_common(graph.number_of_local_nodes() == expected_nodes && + graph.number_of_local_edges() == 0 && + graph.get_from_range() == expected_from && + graph.get_to_range() == expected_from && + permutation.empty() && + graph.get_range_array() == expected_ranges && + graph.get_edge_range_array() == + std::vector{0, 0, 0, 0, 0, 0}, + communicator); + } + remove_fixture(communicator, edgeless_path); + REQUIRE(MPI_Comm_free(&communicator) == MPI_SUCCESS); +} + +TEST_CASE("edge-balanced binary input preserves exact reversed-subcomm order", + "[mpi][parallel-io][edge-balanced][determinism]") { + auto communicator = MPI_COMM_NULL; + auto const world_rank = communicator_rank(MPI_COMM_WORLD); + auto const world_size = communicator_size(MPI_COMM_WORLD); + REQUIRE(world_size == 5); + REQUIRE(MPI_Comm_split(MPI_COMM_WORLD, 0, world_size - world_rank, + &communicator) == MPI_SUCCESS); + auto const rank = communicator_rank(communicator); + + auto const path = shared_fixture_path(communicator, "three-vertices.bgf"); + auto const offsets = std::array{56, 72, 80, 88}; + auto const adjacency = std::array{2, 1, 0, 1}; + write_binary_graph(communicator, path, 3, 4, offsets, adjacency); + + { + auto graph = parhip::parallel_graph_access{communicator}; + auto config = parhip::PPartitionConfig{}; + config.binary_io_window_size = 2; + auto permutation = std::vector{}; + parhip::edge_balanced_graph_io::read_binary_graph_edge_balanced( + graph, path.string(), config, permutation, + parhip::mpi::communicator_view{communicator}); + + auto const expected_ranges = std::vector{0, 1, 1, 2, 3, 3}; + auto const expected_edge_ranges = std::vector{0, 2, 2, 3, 4, 4}; + auto const expected_nodes = std::array{1, 0, 1, 1, 0}; + auto const expected_edges = std::array{2, 0, 1, 1, 0}; + auto const expected_targets = + std::array, 5>{std::vector{1, 2}, + {}, + std::vector{0}, + std::vector{1}, + {}}; + auto const expected_permutations = + std::array, 5>{std::vector{1, 0}, + {}, + std::vector{0}, + std::vector{0}, + {}}; + require_common( + graph.number_of_local_nodes() == + expected_nodes[static_cast(rank)] && + graph.number_of_local_edges() == + expected_edges[static_cast(rank)] && + graph.get_range_array() == expected_ranges && + graph.get_edge_range_array() == expected_edge_ranges && + local_targets(graph) == + expected_targets[static_cast(rank)] && + permutation == + expected_permutations[static_cast(rank)], + communicator); + } + remove_fixture(communicator, path); + REQUIRE(MPI_Comm_free(&communicator) == MPI_SUCCESS); +} diff --git a/parallel/parallel_src/tests/io/parallel_io_failure_probe.cpp b/parallel/parallel_src/tests/io/parallel_io_failure_probe.cpp new file mode 100644 index 00000000..1785b069 --- /dev/null +++ b/parallel/parallel_src/tests/io/parallel_io_failure_probe.cpp @@ -0,0 +1,291 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "data_structure/parallel_graph_access.h" +#include "io/parallel_graph_io.h" +#include "io/parallel_vector_io.h" +#include "partition_config.h" + +// KAHIP_PMPI_CALLBACK_REGION_BEGIN +namespace parallel_io_failure_probe { +enum class mode : unsigned char { + vector_missing, + vector_truncated, + graph_truncated, + text_missing, +}; + +inline bool active = false; +inline mode selected = mode::vector_missing; +inline MPI_Comm expected_communicator = MPI_COMM_NULL; +inline int communicator_rank = -1; +inline int finalizations = 0; +inline std::string fixture; + +void write_text(std::string_view text) noexcept { + static_cast(::write(STDERR_FILENO, text.data(), text.size())); +} + +[[nodiscard]] auto mode_marker() noexcept -> std::string_view { + switch (selected) { + case mode::vector_missing: + return "parallel-io-vector-missing"; + case mode::vector_truncated: + return "parallel-io-vector-truncated"; + case mode::graph_truncated: + return "parallel-io-graph-truncated"; + case mode::text_missing: + return "parallel-io-text-missing"; + } + return "parallel-io-unknown"; +} + +[[noreturn]] void observed_abort(MPI_Comm communicator, + int error_code) noexcept { + int relation = MPI_UNEQUAL; + if (error_code != EXIT_FAILURE || finalizations != 0 || + communicator_rank < 0 || communicator_rank > 1 || + PMPI_Comm_compare(communicator, expected_communicator, &relation) != + MPI_SUCCESS || + (relation != MPI_IDENT && relation != MPI_CONGRUENT)) { + write_text("observed parallel-io MPI_Abort with unexpected state\n"); + std::_Exit(91); + } + if (communicator_rank == 0) { + switch (selected) { + case mode::vector_missing: + write_text( + "observed MPI_Abort rank=0 parallel-io-vector-missing " + "affected-communicator; internal MPI_Finalize counter is zero\n"); + break; + case mode::vector_truncated: + write_text( + "observed MPI_Abort rank=0 parallel-io-vector-truncated " + "affected-communicator; internal MPI_Finalize counter is zero\n"); + break; + case mode::graph_truncated: + write_text( + "observed MPI_Abort rank=0 parallel-io-graph-truncated " + "affected-communicator; internal MPI_Finalize counter is zero\n"); + break; + case mode::text_missing: + write_text( + "observed MPI_Abort rank=0 parallel-io-text-missing " + "affected-communicator; internal MPI_Finalize counter is zero\n"); + break; + } + } else { + switch (selected) { + case mode::vector_missing: + write_text( + "observed MPI_Abort rank=1 parallel-io-vector-missing " + "affected-communicator; internal MPI_Finalize counter is zero\n"); + break; + case mode::vector_truncated: + write_text( + "observed MPI_Abort rank=1 parallel-io-vector-truncated " + "affected-communicator; internal MPI_Finalize counter is zero\n"); + break; + case mode::graph_truncated: + write_text( + "observed MPI_Abort rank=1 parallel-io-graph-truncated " + "affected-communicator; internal MPI_Finalize counter is zero\n"); + break; + case mode::text_missing: + write_text( + "observed MPI_Abort rank=1 parallel-io-text-missing " + "affected-communicator; internal MPI_Finalize counter is zero\n"); + break; + } + } + if (PMPI_Barrier(MPI_COMM_WORLD) != MPI_SUCCESS) { + std::_Exit(90); + } + if (communicator_rank == 0) { + static_cast(::unlink(fixture.c_str())); + } + std::_Exit(86); +} +} // namespace parallel_io_failure_probe + +static_assert(noexcept(parallel_io_failure_probe::write_text({}))); +static_assert(noexcept(parallel_io_failure_probe::mode_marker())); +static_assert(noexcept(parallel_io_failure_probe::observed_abort(MPI_COMM_NULL, + 0))); + +extern "C" int MPI_Finalize() { + if (parallel_io_failure_probe::active) { + ++parallel_io_failure_probe::finalizations; + return MPI_SUCCESS; + } + return PMPI_Finalize(); +} + +extern "C" int MPI_Abort(MPI_Comm communicator, int error_code) { + parallel_io_failure_probe::observed_abort(communicator, error_code); +} +// KAHIP_PMPI_CALLBACK_REGION_END + +namespace { +using parhip::NodeID; +using parhip::ULONG; + +[[nodiscard]] auto parse_mode(std::string_view value) + -> parallel_io_failure_probe::mode { + using mode = parallel_io_failure_probe::mode; + if (value == "vector-missing") { + return mode::vector_missing; + } + if (value == "vector-truncated") { + return mode::vector_truncated; + } + if (value == "graph-truncated") { + return mode::graph_truncated; + } + if (value == "text-missing") { + return mode::text_missing; + } + std::_Exit(2); +} + +void write_fixture(parallel_io_failure_probe::mode selected, + std::string const& filename) { + auto output = std::ofstream{filename, std::ios::binary | std::ios::trunc}; + switch (selected) { + case parallel_io_failure_probe::mode::vector_missing: { + auto const values = std::array{1, 2, 17, 19}; + output.write(reinterpret_cast(values.data()), + static_cast(sizeof(values))); + break; + } + case parallel_io_failure_probe::mode::vector_truncated: { + auto const values = std::array{1, 2, 17}; + output.write(reinterpret_cast(values.data()), + static_cast(sizeof(values))); + break; + } + case parallel_io_failure_probe::mode::graph_truncated: { + auto const header = std::array{3, 2, 2}; + auto const offsets = std::array{48, 56, 64}; + auto const adjacency = std::array{1}; + output.write(reinterpret_cast(header.data()), + static_cast(sizeof(header))); + output.write(reinterpret_cast(offsets.data()), + static_cast(sizeof(offsets))); + output.write(reinterpret_cast(adjacency.data()), + static_cast(sizeof(adjacency))); + break; + } + case parallel_io_failure_probe::mode::text_missing: + output << "2 1\n2\n1\n"; + break; + } + if (!output) { + std::_Exit(3); + } +} + +void build_partition_graph(parhip::parallel_graph_access& graph, int rank) { + graph.start_construction(1, 0, 2, 0, false); + graph.set_range(static_cast(rank), static_cast(rank)); + auto ranges = std::vector{0, 1, 2}; + graph.set_range_array(ranges); + auto const node = graph.new_node(); + graph.setNodeWeight(node, 1); + graph.setNodeLabel(node, 99); + graph.setSecondPartitionIndex(node, 0); + graph.finish_construction(); +} +} // namespace + +int main(int argc, char** argv) { + if (argc != 2 || MPI_Init(&argc, &argv) != MPI_SUCCESS) { + return 2; + } + auto world_rank = 0; + auto world_size = 0; + if (MPI_Comm_rank(MPI_COMM_WORLD, &world_rank) != MPI_SUCCESS || + MPI_Comm_size(MPI_COMM_WORLD, &world_size) != MPI_SUCCESS || + world_size != 2) { + return 3; + } + auto communicator = MPI_COMM_NULL; + if (MPI_Comm_split(MPI_COMM_WORLD, 0, world_size - world_rank, + &communicator) != MPI_SUCCESS || + communicator == MPI_COMM_NULL || + MPI_Comm_set_errhandler(communicator, MPI_ERRORS_RETURN) != MPI_SUCCESS) { + return 4; + } + auto rank = -1; + if (MPI_Comm_rank(communicator, &rank) != MPI_SUCCESS) { + return 5; + } + + auto const selected = parse_mode(argv[1]); + auto filename = std::string{}; + if (rank == 0) { + filename = "/tmp/kahip-parallel-io-failure-" + std::to_string(::getpid()) + + ".fixture"; + write_fixture(selected, filename); + } + std::uint64_t filename_size = filename.size(); + if (MPI_Bcast(&filename_size, 1, MPI_UINT64_T, 0, communicator) != + MPI_SUCCESS || + filename_size > + static_cast(std::numeric_limits::max())) { + return 6; + } + filename.resize(static_cast(filename_size)); + if (MPI_Bcast(filename.data(), static_cast(filename_size), MPI_CHAR, 0, + communicator) != MPI_SUCCESS || + MPI_Barrier(communicator) != MPI_SUCCESS) { + return 7; + } + + parallel_io_failure_probe::selected = selected; + parallel_io_failure_probe::fixture = filename; + parallel_io_failure_probe::expected_communicator = communicator; + parallel_io_failure_probe::communicator_rank = rank; + + auto graph = parhip::parallel_graph_access{communicator}; + auto config = parhip::PPartitionConfig{}; + config.binary_io_window_size = 2; + parallel_io_failure_probe::active = true; + switch (selected) { + case parallel_io_failure_probe::mode::vector_missing: + build_partition_graph(graph, rank); + if (rank == 1) { + filename += ".missing"; + } + parhip::parallel_vector_io{}.readPartitionBinaryParallel(config, graph, + filename); + break; + case parallel_io_failure_probe::mode::vector_truncated: + build_partition_graph(graph, rank); + parhip::parallel_vector_io{}.readPartitionBinaryParallel(config, graph, + filename); + break; + case parallel_io_failure_probe::mode::graph_truncated: + static_cast(parhip::parallel_graph_io::readGraphBinary( + config, graph, filename, rank, 2, communicator)); + break; + case parallel_io_failure_probe::mode::text_missing: + if (rank == 1) { + filename += ".missing"; + } + static_cast(parhip::parallel_graph_io::readGraphWeightedFlexible( + graph, filename, rank, 2, communicator)); + break; + } + parallel_io_failure_probe::write_text("returned-from-failure\n"); + std::_Exit(92); +} diff --git a/parallel/parallel_src/tests/io/parallel_io_mpi_test.cpp b/parallel/parallel_src/tests/io/parallel_io_mpi_test.cpp new file mode 100644 index 00000000..a14695e7 --- /dev/null +++ b/parallel/parallel_src/tests/io/parallel_io_mpi_test.cpp @@ -0,0 +1,387 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "data_structure/parallel_graph_access.h" +#include "io/parallel_graph_io.h" +#include "io/parallel_vector_io.h" +#include "partition_config.h" + +namespace { +using parhip::EdgeID; +using parhip::NodeID; +using parhip::ULONG; + +[[nodiscard]] auto communicator_rank(MPI_Comm communicator) -> int { + auto rank = -1; + REQUIRE(MPI_Comm_rank(communicator, &rank) == MPI_SUCCESS); + return rank; +} + +[[nodiscard]] auto communicator_size(MPI_Comm communicator) -> int { + auto size = 0; + REQUIRE(MPI_Comm_size(communicator, &size) == MPI_SUCCESS); + return size; +} + +[[nodiscard]] auto shared_fixture_path(MPI_Comm communicator, + std::string_view suffix) + -> std::filesystem::path { + auto const rank = communicator_rank(communicator); + auto path = std::string{}; + if (rank == 0) { + path = (std::filesystem::temp_directory_path() / + ("kahip-parallel-io-" + std::to_string(::getpid()) + "-" + + std::string{suffix})) + .string(); + } + auto length = static_cast(path.size()); + REQUIRE(MPI_Bcast(&length, 1, MPI_UINT64_T, 0, communicator) == MPI_SUCCESS); + REQUIRE(length <= + static_cast(std::numeric_limits::max())); + path.resize(static_cast(length)); + REQUIRE(MPI_Bcast(path.data(), static_cast(length), MPI_CHAR, 0, + communicator) == MPI_SUCCESS); + return path; +} + +template +void write_fixture(MPI_Comm communicator, + std::filesystem::path const& path, + Writer&& writer) { + auto const rank = communicator_rank(communicator); + auto success = 1; + if (rank == 0) { + try { + std::invoke(std::forward(writer), path); + } catch (...) { + success = 0; + } + } + REQUIRE(MPI_Bcast(&success, 1, MPI_INT, 0, communicator) == MPI_SUCCESS); + REQUIRE(success == 1); + REQUIRE(MPI_Barrier(communicator) == MPI_SUCCESS); +} + +void remove_fixture(MPI_Comm communicator, std::filesystem::path const& path) { + REQUIRE(MPI_Barrier(communicator) == MPI_SUCCESS); + if (communicator_rank(communicator) == 0) { + std::error_code error; + static_cast(std::filesystem::remove(path, error)); + REQUIRE_FALSE(error); + } + REQUIRE(MPI_Barrier(communicator) == MPI_SUCCESS); +} + +void write_binary_graph(std::filesystem::path const& path, + NodeID nodes, + EdgeID edges, + std::span offsets, + std::span adjacency) { + auto output = std::ofstream{path, std::ios::binary | std::ios::trunc}; + if (!output) { + throw std::ios_base::failure{"unable to create binary graph fixture"}; + } + auto const header = std::array{3, nodes, edges}; + output.write(reinterpret_cast(header.data()), + static_cast(sizeof(header))); + output.write(reinterpret_cast(offsets.data()), + static_cast(offsets.size_bytes())); + if (!adjacency.empty()) { + output.write(reinterpret_cast(adjacency.data()), + static_cast(adjacency.size_bytes())); + } + if (!output) { + throw std::ios_base::failure{"unable to write binary graph fixture"}; + } +} + +void build_label_graph(parhip::parallel_graph_access& graph, + MPI_Comm communicator, + bool empty) { + auto const rank = communicator_rank(communicator); + auto const size = communicator_size(communicator); + auto const global_nodes = empty ? NodeID{0} : NodeID{3}; + auto const local_nodes = empty ? NodeID{0} + : size == 1 ? global_nodes + : rank == 1 ? global_nodes + : NodeID{0}; + auto const from = empty || size == 1 || rank <= 1 ? NodeID{0} : global_nodes; + + graph.start_construction(local_nodes, 0, global_nodes, 0, false); + graph.set_range(from, local_nodes == 0 ? from : from + local_nodes - 1); + auto ranges = + std::vector(static_cast(size) + 1, global_nodes); + ranges.front() = 0; + if (!empty && size > 1) { + ranges[1] = 0; + } + graph.set_range_array(ranges); + for (NodeID local = 0; local < local_nodes; ++local) { + auto const node = graph.new_node(); + graph.setNodeWeight(node, 1); + graph.setNodeLabel(node, 101 + from + local); + graph.setSecondPartitionIndex(node, 0); + } + graph.finish_construction(); +} + +void require_common(bool condition, MPI_Comm communicator) { + auto local = condition ? 1 : 0; + auto global = 0; + REQUIRE(MPI_Allreduce(&local, &global, 1, MPI_INT, MPI_MIN, communicator) == + MPI_SUCCESS); + REQUIRE(global == 1); +} +} // namespace + +TEST_CASE("binary graph ranges clamp to n when ranks outnumber vertices", + "[mpi][parallel-io][graph][binary][zero-work]") { + auto communicator = MPI_COMM_NULL; + auto const world_rank = communicator_rank(MPI_COMM_WORLD); + auto const world_size = communicator_size(MPI_COMM_WORLD); + REQUIRE(MPI_Comm_split(MPI_COMM_WORLD, 0, world_size - world_rank, + &communicator) == MPI_SUCCESS); + auto const rank = communicator_rank(communicator); + auto const size = communicator_size(communicator); + auto const path = shared_fixture_path(communicator, "small.bgf"); + write_fixture(communicator, path, [&](auto const& fixture) { + auto const offsets = std::array{48, 56, 64}; + auto const adjacency = std::array{1, 0}; + write_binary_graph(fixture, 2, 2, offsets, adjacency); + }); + + { + auto graph = parhip::parallel_graph_access{communicator}; + auto config = parhip::PPartitionConfig{}; + config.binary_io_window_size = 2; + REQUIRE(parhip::parallel_graph_io::readGraphBinary( + config, graph, path.string(), rank, size, communicator) == 0); + + auto const expected_from = + size == 1 ? NodeID{0} : std::min(rank, NodeID{2}); + auto const expected_nodes = size == 1 ? NodeID{2} + : rank < 2 ? NodeID{1} + : NodeID{0}; + auto const expected_to = expected_nodes == 0 + ? expected_from + : expected_from + expected_nodes - 1; + auto exact = graph.number_of_local_nodes() == expected_nodes && + graph.get_from_range() == expected_from && + graph.get_to_range() == expected_to && + graph.get_range_array().back() == NodeID{2}; + for (auto boundary : graph.get_range_array()) { + exact = exact && boundary <= NodeID{2}; + } + for (NodeID local = 0; exact && local < expected_nodes; ++local) { + auto const global = expected_from + local; + auto const edge = graph.get_first_edge(local); + exact = + graph.getNodeDegree(local) == 1 && + graph.getGlobalID(graph.getEdgeTarget(edge)) == NodeID{1} - global && + graph.getEdgeWeight(edge) == 1; + } + require_common(exact, communicator); + } + remove_fixture(communicator, path); + REQUIRE(MPI_Comm_free(&communicator) == MPI_SUCCESS); +} + +TEST_CASE("empty text and binary graphs remain empty on every rank", + "[mpi][parallel-io][graph][empty]") { + auto const rank = communicator_rank(MPI_COMM_WORLD); + auto const size = communicator_size(MPI_COMM_WORLD); + auto const binary_path = shared_fixture_path(MPI_COMM_WORLD, "empty.bgf"); + auto const text_path = shared_fixture_path(MPI_COMM_WORLD, "empty.graph"); + write_fixture(MPI_COMM_WORLD, binary_path, [&](auto const& fixture) { + auto const offsets = std::array{32}; + write_binary_graph(fixture, 0, 0, offsets, {}); + }); + write_fixture(MPI_COMM_WORLD, text_path, [&](auto const& fixture) { + auto output = std::ofstream{fixture, std::ios::trunc}; + output << "0 0\n"; + if (!output) { + throw std::ios_base::failure{"unable to write empty text graph"}; + } + }); + + auto config = parhip::PPartitionConfig{}; + config.binary_io_window_size = 2; + auto binary = parhip::parallel_graph_access{MPI_COMM_WORLD}; + REQUIRE(parhip::parallel_graph_io::readGraphBinary( + config, binary, binary_path.string(), rank, size, + MPI_COMM_WORLD) == 0); + auto text = parhip::parallel_graph_access{MPI_COMM_WORLD}; + REQUIRE(parhip::parallel_graph_io::readGraphWeightedFlexible( + text, text_path.string(), rank, size, MPI_COMM_WORLD) == 0); + require_common( + binary.number_of_local_nodes() == 0 && + binary.number_of_local_edges() == 0 && binary.get_from_range() == 0 && + binary.get_to_range() == 0 && binary.get_range_array().back() == 0 && + text.number_of_local_nodes() == 0 && + text.number_of_local_edges() == 0 && text.get_from_range() == 0 && + text.get_to_range() == 0 && text.get_range_array().back() == 0, + MPI_COMM_WORLD); + remove_fixture(MPI_COMM_WORLD, binary_path); + remove_fixture(MPI_COMM_WORLD, text_path); +} + +TEST_CASE("weighted METIS input preserves weights on uneven rank layouts", + "[mpi][parallel-io][graph][weighted][zero-work]") { + auto const rank = communicator_rank(MPI_COMM_WORLD); + auto const size = communicator_size(MPI_COMM_WORLD); + auto const path = shared_fixture_path(MPI_COMM_WORLD, "weighted.graph"); + write_fixture(MPI_COMM_WORLD, path, [&](auto const& fixture) { + auto output = std::ofstream{fixture, std::ios::trunc}; + output << "4 3 11\n" + "5 2 7\n" + "6 1 7 3 9\n" + "8 2 9 4 11\n" + "10 3 11\n"; + if (!output) { + throw std::ios_base::failure{"unable to write weighted text graph"}; + } + }); + + auto graph = parhip::parallel_graph_access{MPI_COMM_WORLD}; + REQUIRE(parhip::parallel_graph_io::readGraphWeightedFlexible( + graph, path.string(), rank, size, MPI_COMM_WORLD) == 0); + + constexpr auto weights = std::array{5, 6, 8, 10}; + constexpr auto targets = std::array, 4>{ + std::array{1, 0}, std::array{0, 2}, + std::array{1, 3}, std::array{2, 0}}; + constexpr auto edge_weights = std::array, 4>{ + std::array{7, 0}, std::array{7, 9}, + std::array{9, 11}, std::array{11, 0}}; + constexpr auto degrees = std::array{1, 2, 2, 1}; + + auto exact = graph.number_of_global_nodes() == 4 && + graph.number_of_global_edges() == 6 && + graph.get_range_array().back() == 4; + for (auto boundary : graph.get_range_array()) { + exact = exact && boundary <= NodeID{4}; + } + for (NodeID local = 0; exact && local < graph.number_of_local_nodes(); + ++local) { + auto const global = graph.get_from_range() + local; + exact = global < 4 && graph.getNodeWeight(local) == weights[global] && + graph.getNodeDegree(local) == degrees[global]; + auto edge = graph.get_first_edge(local); + for (EdgeID offset = 0; exact && offset < degrees[global]; ++offset) { + exact = + graph.getGlobalID(graph.getEdgeTarget(edge + offset)) == + targets[global][offset] && + graph.getEdgeWeight(edge + offset) == edge_weights[global][offset]; + } + } + require_common(exact, MPI_COMM_WORLD); + remove_fixture(MPI_COMM_WORLD, path); +} + +TEST_CASE("empty binary partitions truncate stale data with a zero window", + "[mpi][parallel-io][vector][empty][zero-window]") { + auto const path = shared_fixture_path(MPI_COMM_WORLD, "empty.binp"); + write_fixture(MPI_COMM_WORLD, path, [&](auto const& fixture) { + auto output = std::ofstream{fixture, std::ios::binary | std::ios::trunc}; + auto stale = std::array{}; + output.write(reinterpret_cast(stale.data()), + static_cast(stale.size())); + }); + auto graph = parhip::parallel_graph_access{MPI_COMM_WORLD}; + build_label_graph(graph, MPI_COMM_WORLD, true); + auto config = parhip::PPartitionConfig{}; + config.binary_io_window_size = 0; + auto io = parhip::parallel_vector_io{}; + io.writePartitionBinaryParallelPosix(config, graph, path.string()); + io.readPartitionBinaryParallel(config, graph, path.string()); + + auto exact_size = 0; + if (communicator_rank(MPI_COMM_WORLD) == 0) { + std::error_code error; + exact_size = + std::filesystem::file_size(path, error) == 2 * sizeof(ULONG) && !error + ? 1 + : 0; + } + REQUIRE(MPI_Bcast(&exact_size, 1, MPI_INT, 0, MPI_COMM_WORLD) == MPI_SUCCESS); + require_common(exact_size == 1 && graph.number_of_local_nodes() == 0, + MPI_COMM_WORLD); + remove_fixture(MPI_COMM_WORLD, path); +} + +TEST_CASE( + "binary partition roundtrip supports leading and trailing zero-work " + "ranks", + "[mpi][parallel-io][vector][binary][zero-work]") { + auto const path = shared_fixture_path(MPI_COMM_WORLD, "labels.binp"); + auto graph = parhip::parallel_graph_access{MPI_COMM_WORLD}; + build_label_graph(graph, MPI_COMM_WORLD, false); + auto config = parhip::PPartitionConfig{}; + config.binary_io_window_size = 2; + auto io = parhip::parallel_vector_io{}; + io.writePartitionBinaryParallelPosix(config, graph, path.string()); + for (NodeID node = 0; node < graph.number_of_local_nodes(); ++node) { + graph.setNodeLabel(node, 0); + } + io.readPartitionBinaryParallel(config, graph, path.string()); + auto exact = true; + for (NodeID node = 0; node < graph.number_of_local_nodes(); ++node) { + exact = exact && + graph.getNodeLabel(node) == 101 + graph.get_from_range() + node; + } + require_common(exact, MPI_COMM_WORLD); + remove_fixture(MPI_COMM_WORLD, path); +} + +TEST_CASE("text partition ordering follows the graph communicator", + "[mpi][parallel-io][vector][text][communicator]") { + auto const world_rank = communicator_rank(MPI_COMM_WORLD); + auto const world_size = communicator_size(MPI_COMM_WORLD); + auto communicator = MPI_COMM_NULL; + REQUIRE(MPI_Comm_split(MPI_COMM_WORLD, 0, world_size - world_rank, + &communicator) == MPI_SUCCESS); + auto const rank = communicator_rank(communicator); + auto const size = communicator_size(communicator); + auto const path = shared_fixture_path(communicator, "labels.txtp"); + + { + auto graph = parhip::parallel_graph_access{communicator}; + graph.start_construction(1, 0, static_cast(size), 0, false); + graph.set_range(static_cast(rank), static_cast(rank)); + auto ranges = std::vector(static_cast(size) + 1); + for (int pe = 0; pe <= size; ++pe) { + ranges[static_cast(pe)] = static_cast(pe); + } + graph.set_range_array(ranges); + auto const node = graph.new_node(); + graph.setNodeWeight(node, 1); + graph.setNodeLabel(node, 501 + static_cast(rank)); + graph.setSecondPartitionIndex(node, 0); + graph.finish_construction(); + + auto io = parhip::parallel_vector_io{}; + io.writePartitionSimpleParallel(graph, path.string()); + graph.setNodeLabel(0, 0); + io.readPartitionSimpleParallel(graph, path.string()); + require_common(graph.getNodeLabel(0) == 501 + static_cast(rank), + communicator); + } + remove_fixture(communicator, path); + REQUIRE(MPI_Comm_free(&communicator) == MPI_SUCCESS); +} diff --git a/parallel/parallel_src/tests/parallel_contraction/parallel_contraction_mpi_test.cpp b/parallel/parallel_src/tests/parallel_contraction/parallel_contraction_mpi_test.cpp new file mode 100644 index 00000000..4f564213 --- /dev/null +++ b/parallel/parallel_src/tests/parallel_contraction/parallel_contraction_mpi_test.cpp @@ -0,0 +1,3241 @@ +// +// Created by Erich Essmann on 16/08/2024. +// +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "communication/contiguous_owner_layout.h" +#include "communication/ghost_exchange_plan.h" +#include "communication/mpi_error.h" +#include "communication/mpi_tools.h" +#include "communication/mpi_trace.h" +#include "distributed_partitioning/distributed_partitioner.h" +#include "kahip_mpi_capabilities.h" +#include "parallel_contraction_projection/parallel_contraction.h" + +using namespace parhip; + +namespace { +template +void write_range(std::ostream& output, Range const& values) { + output << '['; + auto first = true; + for (auto const& value : values) { + if (!first) { + output << ", "; + } + output << value; + first = false; + } + output << ']'; +} + +template +void write_nested_range(std::ostream& output, Range const& values) { + output << '['; + auto first = true; + for (auto const& value : values) { + if (!first) { + output << ", "; + } + write_range(output, value); + first = false; + } + output << ']'; +} +} // namespace + +namespace protocol_probe { +template +class fixed_log final { + public: + void clear() noexcept { + size_ = 0; + overflowed_ = false; + } + + void push_back(T value) noexcept { + if (size_ == Capacity) { + overflowed_ = true; + return; + } + values_[size_++] = value; + } + + [[nodiscard]] auto begin() const noexcept { return values_.begin(); } + [[nodiscard]] auto end() const noexcept { + return values_.begin() + static_cast(size_); + } + [[nodiscard]] auto size() const noexcept -> std::size_t { return size_; } + [[nodiscard]] auto overflowed() const noexcept -> bool { return overflowed_; } + + friend auto operator==(fixed_log const& lhs, std::vector const& rhs) + -> bool { + return std::ranges::equal(lhs, rhs); + } + + private: + std::array values_{}; + std::size_t size_ = 0; + bool overflowed_ = false; +}; + +inline bool active = false; +inline int all_to_all_v_calls = 0; +inline int all_to_all_v_c_calls = 0; +inline int topology_create_calls = 0; +inline int neighbor_count_exchange_calls = 0; +inline int neighbor_payload_calls = 0; +inline int neighbor_payload_c_calls = 0; +inline int point_to_point_calls = 0; +inline int immediate_neighbor_calls = 0; +inline int persistent_calls = 0; +inline int completion_calls = 0; +inline int barrier_calls = 0; +inline int isend_calls = 0; +inline int probe_calls = 0; +inline int recv_calls = 0; +inline bool interposer_error = false; +inline fixed_log payload_extents; +inline fixed_log isend_tags; +inline fixed_log probe_tags; +inline fixed_log recv_tags; +inline fixed_log neighbor_sources; +inline fixed_log neighbor_destinations; +inline fixed_log neighbor_send_counts; +inline bool ghost_corruption_fired = false; +inline NodeID ghost_coarse_domain = 0; +inline NodeID ghost_replacement_global_id = 0; + +enum class receive_mutation { + none, + label_request_wrong_owner, + label_reply_bad_correlation, + label_reply_coarse_id_out_of_domain, + quotient_edge_wrong_owner, + quotient_edge_target_out_of_domain, + quotient_edge_sequence_gap, + quotient_node_weight_wrong_owner, + ghost_cnode_unknown_id, + ghost_cnode_wrong_source, + ghost_cnode_duplicate_replacing_missing, + ghost_cnode_missing_extra, + ghost_cnode_coarse_id_out_of_domain, + ghost_weight_unknown_id, + ghost_weight_wrong_source, + ghost_weight_duplicate_replacing_missing, + ghost_weight_missing_extra, +}; + +inline receive_mutation mutation = receive_mutation::none; +inline int mutation_payload_ordinal = 0; +inline int mutation_target_rank = 0; + +void reset() { + all_to_all_v_calls = 0; + all_to_all_v_c_calls = 0; + topology_create_calls = 0; + neighbor_count_exchange_calls = 0; + neighbor_payload_calls = 0; + neighbor_payload_c_calls = 0; + point_to_point_calls = 0; + immediate_neighbor_calls = 0; + persistent_calls = 0; + completion_calls = 0; + barrier_calls = 0; + isend_calls = 0; + probe_calls = 0; + recv_calls = 0; + interposer_error = false; + payload_extents.clear(); + isend_tags.clear(); + probe_tags.clear(); + recv_tags.clear(); + neighbor_sources.clear(); + neighbor_destinations.clear(); + neighbor_send_counts.clear(); + ghost_corruption_fired = false; + ghost_coarse_domain = 0; + ghost_replacement_global_id = 0; + mutation = receive_mutation::none; + mutation_payload_ordinal = 0; + mutation_target_rank = 0; +} + +[[nodiscard]] auto dense_payload_collective_calls() -> int { + return all_to_all_v_calls + all_to_all_v_c_calls; +} + +[[nodiscard]] auto blocking_neighbor_payload_calls() -> int { + return neighbor_payload_calls + neighbor_payload_c_calls; +} + +class activation final { + public: + explicit activation(receive_mutation selected = receive_mutation::none, + int target_rank = 0, + NodeID coarse_domain = 0, + NodeID replacement_global_id = 0) { + reset(); + mutation = selected; + mutation_target_rank = target_rank; + ghost_coarse_domain = coarse_domain; + ghost_replacement_global_id = replacement_global_id; + active = true; + } + ~activation() { active = false; } + + activation(activation const&) = delete; + auto operator=(activation const&) -> activation& = delete; +}; + +void record_payload_extent(MPI_Datatype datatype) noexcept { + MPI_Aint lower_bound = 0; + MPI_Aint extent = 0; + if (PMPI_Type_get_extent(datatype, &lower_bound, &extent) != MPI_SUCCESS || + lower_bound != 0) { + interposer_error = true; + return; + } + payload_extents.push_back(extent); +} + +template +[[nodiscard]] auto calls_in_tag_phase(Tags const& tags, int phase, int size) + -> std::size_t { + auto const first = phase * size; + auto const last = (phase + 1) * size; + return static_cast(std::ranges::count_if(tags, [&](int tag) { + return first <= tag && tag < last; + })); +} + +[[nodiscard]] auto payload_calls_with_extent(MPI_Aint extent) -> std::size_t { + return static_cast( + std::ranges::count(payload_extents, extent)); +} + +class scoped_receive_mutation { +public: + scoped_receive_mutation(receive_mutation selected, + int payload_ordinal, + int target_rank = 0) noexcept { + mutation = selected; + mutation_payload_ordinal = payload_ordinal; + mutation_target_rank = target_rank; + } + + ~scoped_receive_mutation() noexcept { + mutation = receive_mutation::none; + mutation_payload_ordinal = 0; + mutation_target_rank = 0; + } + + scoped_receive_mutation(scoped_receive_mutation const&) = delete; + auto operator=(scoped_receive_mutation const&) + -> scoped_receive_mutation& = delete; +}; + +template +void mutate_received_payload(int payload_ordinal, + void* receive_buffer, + Count const receive_counts[], + Displacement const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) noexcept { + if (mutation == receive_mutation::none || + payload_ordinal != mutation_payload_ordinal) { + return; + } + + int rank = 0; + int size = 0; + if (PMPI_Comm_rank(communicator, &rank) != MPI_SUCCESS || + PMPI_Comm_size(communicator, &size) != MPI_SUCCESS) { + interposer_error = true; + return; + } + if (rank != mutation_target_rank) { + return; + } + + MPI_Aint lower_bound = 0; + MPI_Aint extent = 0; + if (PMPI_Type_get_extent(receive_datatype, &lower_bound, &extent) != + MPI_SUCCESS || + lower_bound != 0) { + interposer_error = true; + return; + } + for (int source = 0; source < size; ++source) { + if (receive_counts[source] <= 0) { + continue; + } + auto* record = static_cast(receive_buffer) + + static_cast(receive_displacements[source]) * + extent; + switch (mutation) { + case receive_mutation::label_request_wrong_owner: + reinterpret_cast(record)->old_label = + NodeID{2}; + break; + case receive_mutation::quotient_edge_wrong_owner: + reinterpret_cast(record)->source = + NodeID{2}; + break; + case receive_mutation::quotient_edge_target_out_of_domain: + reinterpret_cast(record)->target = + NodeID{4}; + break; + case receive_mutation::quotient_node_weight_wrong_owner: + reinterpret_cast(record) + ->coarse_global_id = NodeID{2}; + break; + case receive_mutation::label_reply_bad_correlation: + // Rank 0 requested label 3 from source 1 in this fixture. Label 2 is + // still in-domain and owned by source 1, but it is not a key rank 0 + // requested, so only semantic correlation rejects it. + reinterpret_cast(record)->old_label = + NodeID{2}; + break; + case receive_mutation::label_reply_coarse_id_out_of_domain: + // The fixture has exactly three distinct labels, so ID 3 is the + // first invalid half-open coarse ID and exercises the received bound. + reinterpret_cast(record) + ->coarse_global_id = NodeID{3}; + break; + case receive_mutation::quotient_edge_sequence_gap: + ++reinterpret_cast(record) + ->sender_sequence; + break; + case receive_mutation::ghost_cnode_unknown_id: + case receive_mutation::ghost_cnode_wrong_source: + case receive_mutation::ghost_cnode_duplicate_replacing_missing: + case receive_mutation::ghost_cnode_missing_extra: + case receive_mutation::ghost_cnode_coarse_id_out_of_domain: + case receive_mutation::ghost_weight_unknown_id: + case receive_mutation::ghost_weight_wrong_source: + case receive_mutation::ghost_weight_duplicate_replacing_missing: + case receive_mutation::ghost_weight_missing_extra: + interposer_error = true; + return; + case receive_mutation::none: + break; + } + return; + } + interposer_error = true; +} + +void record_graph_neighbors(MPI_Comm communicator) noexcept { + auto indegree = 0; + auto outdegree = 0; + auto weighted = 0; + if (PMPI_Dist_graph_neighbors_count(communicator, &indegree, &outdegree, + &weighted) != MPI_SUCCESS || + indegree < 0 || outdegree < 0 || indegree > 32 || outdegree > 32) { + interposer_error = true; + return; + } + auto sources = std::array{}; + auto destinations = std::array{}; + if (PMPI_Dist_graph_neighbors(communicator, indegree, sources.data(), + MPI_UNWEIGHTED, outdegree, destinations.data(), + MPI_UNWEIGHTED) != MPI_SUCCESS) { + interposer_error = true; + return; + } + for (auto index = 0; index < indegree; ++index) { + neighbor_sources.push_back(sources[static_cast(index)]); + } + for (auto index = 0; index < outdegree; ++index) { + neighbor_destinations.push_back( + destinations[static_cast(index)]); + } +} + +template +void record_neighbor_send_counts(Count const counts[], + MPI_Comm communicator) noexcept { + auto indegree = 0; + auto outdegree = 0; + auto weighted = 0; + if (PMPI_Dist_graph_neighbors_count(communicator, &indegree, &outdegree, + &weighted) != MPI_SUCCESS || + outdegree < 0 || outdegree > 32 || + (outdegree != 0 && counts == nullptr)) { + interposer_error = true; + return; + } + for (auto index = 0; index < outdegree; ++index) { + auto const value = counts[index]; + if (!std::in_range(value)) { + interposer_error = true; + return; + } + neighbor_send_counts.push_back(static_cast(value)); + } +} + +template +void mutate_neighbor_payload(void* receive_buffer, + Count const receive_counts[], + Displacement const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) noexcept { + auto const is_cnode_mutation = + mutation == receive_mutation::ghost_cnode_unknown_id || + mutation == receive_mutation::ghost_cnode_wrong_source || + mutation == receive_mutation::ghost_cnode_duplicate_replacing_missing || + mutation == receive_mutation::ghost_cnode_missing_extra || + mutation == receive_mutation::ghost_cnode_coarse_id_out_of_domain; + auto const is_weight_mutation = + mutation == receive_mutation::ghost_weight_unknown_id || + mutation == receive_mutation::ghost_weight_wrong_source || + mutation == receive_mutation::ghost_weight_duplicate_replacing_missing || + mutation == receive_mutation::ghost_weight_missing_extra; + auto const is_ghost_mutation = is_cnode_mutation || is_weight_mutation; + if (!active || !is_ghost_mutation) { + return; + } + + auto rank = 0; + auto indegree = 0; + auto outdegree = 0; + auto weighted = 0; + if (PMPI_Comm_rank(communicator, &rank) != MPI_SUCCESS || + PMPI_Dist_graph_neighbors_count(communicator, &indegree, &outdegree, + &weighted) != MPI_SUCCESS || + indegree < 0 || indegree > 32 || + (indegree != 0 && + (receive_buffer == nullptr || receive_counts == nullptr || + receive_displacements == nullptr))) { + interposer_error = true; + return; + } + if (rank != mutation_target_rank) { + return; + } + + auto lower_bound = MPI_Aint{0}; + auto extent = MPI_Aint{0}; + if (PMPI_Type_get_extent(receive_datatype, &lower_bound, &extent) != + MPI_SUCCESS || + lower_bound != 0 || + (is_cnode_mutation && + extent != static_cast( + sizeof(contraction::ghost_cnode_assignment))) || + (is_weight_mutation && extent != static_cast(sizeof( + contraction::ghost_node_weight)))) { + interposer_error = true; + return; + } + + auto nonempty = std::array{}; + auto nonempty_count = std::size_t{0}; + for (auto index = 0; index < indegree; ++index) { + if (receive_counts[index] > 0) { + nonempty[nonempty_count++] = index; + } + } + if (nonempty_count == 0) { + interposer_error = true; + return; + } + + auto const first = nonempty[0]; + if (!std::in_range(receive_displacements[first])) { + interposer_error = true; + return; + } + auto const first_offset = + static_cast(receive_displacements[first]); + if (is_weight_mutation) { + auto* records = + static_cast(receive_buffer); + switch (mutation) { + case receive_mutation::ghost_weight_unknown_id: + records[first_offset].global_id = std::numeric_limits::max(); + ghost_corruption_fired = true; + return; + case receive_mutation::ghost_weight_wrong_source: + if (nonempty_count < 2 || + !std::in_range(receive_displacements[nonempty[1]])) { + interposer_error = true; + return; + } + records[first_offset] = records[static_cast( + receive_displacements[nonempty[1]])]; + ghost_corruption_fired = true; + return; + case receive_mutation::ghost_weight_duplicate_replacing_missing: + if (receive_counts[first] < 2) { + interposer_error = true; + return; + } + records[first_offset + 1] = records[first_offset]; + ghost_corruption_fired = true; + return; + case receive_mutation::ghost_weight_missing_extra: + records[first_offset].global_id = ghost_replacement_global_id; + ghost_corruption_fired = true; + return; + default: + interposer_error = true; + return; + } + } + + auto* records = + static_cast(receive_buffer); + switch (mutation) { + case receive_mutation::ghost_cnode_unknown_id: + records[first_offset].global_id = std::numeric_limits::max(); + ghost_corruption_fired = true; + break; + case receive_mutation::ghost_cnode_wrong_source: + if (nonempty_count < 2 || + !std::in_range(receive_displacements[nonempty[1]])) { + interposer_error = true; + return; + } + records[first_offset] = + records[static_cast(receive_displacements[nonempty[1]])]; + ghost_corruption_fired = true; + break; + case receive_mutation::ghost_cnode_duplicate_replacing_missing: + if (receive_counts[first] < 2) { + interposer_error = true; + return; + } + records[first_offset + 1] = records[first_offset]; + ghost_corruption_fired = true; + break; + case receive_mutation::ghost_cnode_missing_extra: + records[first_offset].global_id = ghost_replacement_global_id; + ghost_corruption_fired = true; + break; + case receive_mutation::ghost_cnode_coarse_id_out_of_domain: + records[first_offset].coarse_global_id = ghost_coarse_domain; + ghost_corruption_fired = true; + break; + case receive_mutation::none: + case receive_mutation::label_request_wrong_owner: + case receive_mutation::label_reply_bad_correlation: + case receive_mutation::label_reply_coarse_id_out_of_domain: + case receive_mutation::quotient_edge_wrong_owner: + case receive_mutation::quotient_edge_target_out_of_domain: + case receive_mutation::quotient_edge_sequence_gap: + case receive_mutation::quotient_node_weight_wrong_owner: + case receive_mutation::ghost_weight_unknown_id: + case receive_mutation::ghost_weight_wrong_source: + case receive_mutation::ghost_weight_duplicate_replacing_missing: + case receive_mutation::ghost_weight_missing_extra: + interposer_error = true; + break; + } +} +} // namespace protocol_probe + +extern "C" int MPI_Alltoallv(const void* send_buffer, + const int send_counts[], + const int send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + const int receive_counts[], + const int receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (protocol_probe::active) { + ++protocol_probe::all_to_all_v_calls; + protocol_probe::record_payload_extent(send_datatype); + } + auto const payload_ordinal = protocol_probe::dense_payload_collective_calls(); + auto const result = PMPI_Alltoallv(send_buffer, + send_counts, + send_displacements, + send_datatype, + receive_buffer, + receive_counts, + receive_displacements, + receive_datatype, + communicator); + if (protocol_probe::active && result == MPI_SUCCESS) { + protocol_probe::mutate_received_payload(payload_ordinal, + receive_buffer, + receive_counts, + receive_displacements, + receive_datatype, + communicator); + } + return result; +} + +#if KAHIP_HAVE_MPI_ALLTOALLV_C +extern "C" int MPI_Alltoallv_c(const void* send_buffer, + const MPI_Count send_counts[], + const MPI_Aint send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + const MPI_Count receive_counts[], + const MPI_Aint receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (protocol_probe::active) { + ++protocol_probe::all_to_all_v_c_calls; + protocol_probe::record_payload_extent(send_datatype); + } + auto const payload_ordinal = protocol_probe::dense_payload_collective_calls(); + auto const result = PMPI_Alltoallv_c(send_buffer, + send_counts, + send_displacements, + send_datatype, + receive_buffer, + receive_counts, + receive_displacements, + receive_datatype, + communicator); + if (protocol_probe::active && result == MPI_SUCCESS) { + protocol_probe::mutate_received_payload(payload_ordinal, + receive_buffer, + receive_counts, + receive_displacements, + receive_datatype, + communicator); + } + return result; +} +#endif + +extern "C" int MPI_Dist_graph_create(MPI_Comm communicator, + int source_count, + int const sources[], + int const degrees[], + int const destinations[], + int const weights[], + MPI_Info info, + int reorder, + MPI_Comm* graph_communicator) { + if (protocol_probe::active) { + ++protocol_probe::topology_create_calls; + } + auto const result = PMPI_Dist_graph_create( + communicator, source_count, sources, degrees, destinations, weights, info, + reorder, graph_communicator); + if (protocol_probe::active && result == MPI_SUCCESS && + graph_communicator != nullptr && *graph_communicator != MPI_COMM_NULL) { + protocol_probe::record_graph_neighbors(*graph_communicator); + } + return result; +} + +extern "C" int MPI_Neighbor_alltoall(void const* send_buffer, + int send_count, + MPI_Datatype send_datatype, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (protocol_probe::active) { + ++protocol_probe::neighbor_count_exchange_calls; + } + return PMPI_Neighbor_alltoall(send_buffer, send_count, send_datatype, + receive_buffer, receive_count, receive_datatype, + communicator); +} + +extern "C" int MPI_Neighbor_alltoallv(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (protocol_probe::active) { + ++protocol_probe::neighbor_payload_calls; + protocol_probe::record_payload_extent(send_datatype); + protocol_probe::record_neighbor_send_counts(send_counts, communicator); + } + auto const result = PMPI_Neighbor_alltoallv( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator); + if (protocol_probe::active && result == MPI_SUCCESS) { + protocol_probe::mutate_neighbor_payload(receive_buffer, receive_counts, + receive_displacements, + receive_datatype, communicator); + } + return result; +} + +#if KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C +extern "C" int MPI_Neighbor_alltoallv_c(void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (protocol_probe::active) { + ++protocol_probe::neighbor_payload_c_calls; + protocol_probe::record_payload_extent(send_datatype); + protocol_probe::record_neighbor_send_counts(send_counts, communicator); + } + auto const result = PMPI_Neighbor_alltoallv_c( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator); + if (protocol_probe::active && result == MPI_SUCCESS) { + protocol_probe::mutate_neighbor_payload(receive_buffer, receive_counts, + receive_displacements, + receive_datatype, communicator); + } + return result; +} +#endif + +extern "C" int MPI_Isend(const void* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator, + MPI_Request* request) { + if (protocol_probe::active) { + ++protocol_probe::point_to_point_calls; + ++protocol_probe::isend_calls; + protocol_probe::isend_tags.push_back(tag); + } + return PMPI_Isend( + buffer, count, datatype, destination, tag, communicator, request); +} + +extern "C" int MPI_Probe(int source, + int tag, + MPI_Comm communicator, + MPI_Status* status) { + if (protocol_probe::active) { + ++protocol_probe::point_to_point_calls; + ++protocol_probe::probe_calls; + protocol_probe::probe_tags.push_back(tag); + } + return PMPI_Probe(source, tag, communicator, status); +} + +extern "C" int MPI_Recv(void* buffer, + int count, + MPI_Datatype datatype, + int source, + int tag, + MPI_Comm communicator, + MPI_Status* status) { + if (protocol_probe::active) { + ++protocol_probe::point_to_point_calls; + ++protocol_probe::recv_calls; + protocol_probe::recv_tags.push_back(tag); + } + return PMPI_Recv( + buffer, count, datatype, source, tag, communicator, status); +} + +#define KAHIP_CONTRACTION_P2P_WRAPPER(name, signature, arguments) \ + extern "C" int name signature { \ + if (protocol_probe::active) { \ + ++protocol_probe::point_to_point_calls; \ + } \ + return P##name arguments; \ + } + +KAHIP_CONTRACTION_P2P_WRAPPER( + MPI_Send, + (void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator), + (buffer, count, datatype, destination, tag, communicator)) +KAHIP_CONTRACTION_P2P_WRAPPER( + MPI_Ssend, + (void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator), + (buffer, count, datatype, destination, tag, communicator)) +KAHIP_CONTRACTION_P2P_WRAPPER( + MPI_Bsend, + (void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator), + (buffer, count, datatype, destination, tag, communicator)) +KAHIP_CONTRACTION_P2P_WRAPPER( + MPI_Rsend, + (void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator), + (buffer, count, datatype, destination, tag, communicator)) +KAHIP_CONTRACTION_P2P_WRAPPER( + MPI_Issend, + (void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator, + MPI_Request* request), + (buffer, count, datatype, destination, tag, communicator, request)) +KAHIP_CONTRACTION_P2P_WRAPPER( + MPI_Ibsend, + (void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator, + MPI_Request* request), + (buffer, count, datatype, destination, tag, communicator, request)) +KAHIP_CONTRACTION_P2P_WRAPPER( + MPI_Irsend, + (void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator, + MPI_Request* request), + (buffer, count, datatype, destination, tag, communicator, request)) +KAHIP_CONTRACTION_P2P_WRAPPER( + MPI_Irecv, + (void* buffer, + int count, + MPI_Datatype datatype, + int source, + int tag, + MPI_Comm communicator, + MPI_Request* request), + (buffer, count, datatype, source, tag, communicator, request)) +KAHIP_CONTRACTION_P2P_WRAPPER( + MPI_Iprobe, + (int source, int tag, MPI_Comm communicator, int* flag, MPI_Status* status), + (source, tag, communicator, flag, status)) +KAHIP_CONTRACTION_P2P_WRAPPER(MPI_Sendrecv, + (void const* send_buffer, + int send_count, + MPI_Datatype send_datatype, + int destination, + int send_tag, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_datatype, + int source, + int receive_tag, + MPI_Comm communicator, + MPI_Status* status), + (send_buffer, + send_count, + send_datatype, + destination, + send_tag, + receive_buffer, + receive_count, + receive_datatype, + source, + receive_tag, + communicator, + status)) +KAHIP_CONTRACTION_P2P_WRAPPER(MPI_Sendrecv_replace, + (void* buffer, + int count, + MPI_Datatype datatype, + int destination, + int send_tag, + int source, + int receive_tag, + MPI_Comm communicator, + MPI_Status* status), + (buffer, + count, + datatype, + destination, + send_tag, + source, + receive_tag, + communicator, + status)) +KAHIP_CONTRACTION_P2P_WRAPPER(MPI_Mprobe, + (int source, + int tag, + MPI_Comm communicator, + MPI_Message* message, + MPI_Status* status), + (source, tag, communicator, message, status)) +KAHIP_CONTRACTION_P2P_WRAPPER( + MPI_Improbe, + (int source, + int tag, + MPI_Comm communicator, + int* flag, + MPI_Message* message, + MPI_Status* status), + (source, tag, communicator, flag, message, status)) +KAHIP_CONTRACTION_P2P_WRAPPER(MPI_Mrecv, + (void* buffer, + int count, + MPI_Datatype datatype, + MPI_Message* message, + MPI_Status* status), + (buffer, count, datatype, message, status)) +KAHIP_CONTRACTION_P2P_WRAPPER(MPI_Imrecv, + (void* buffer, + int count, + MPI_Datatype datatype, + MPI_Message* message, + MPI_Request* request), + (buffer, count, datatype, message, request)) + +#undef KAHIP_CONTRACTION_P2P_WRAPPER + +extern "C" int MPI_Ineighbor_alltoallv(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Request* request) { + if (protocol_probe::active) { + ++protocol_probe::immediate_neighbor_calls; + } + return PMPI_Ineighbor_alltoallv(send_buffer, send_counts, send_displacements, + send_datatype, receive_buffer, receive_counts, + receive_displacements, receive_datatype, + communicator, request); +} + +#if KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV_C +extern "C" int MPI_Ineighbor_alltoallv_c(void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Request* request) { + if (protocol_probe::active) { + ++protocol_probe::immediate_neighbor_calls; + } + return PMPI_Ineighbor_alltoallv_c( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator, request); +} +#endif + +#if KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT +extern "C" int MPI_Neighbor_alltoallv_init(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Info info, + MPI_Request* request) { + if (protocol_probe::active) { + ++protocol_probe::persistent_calls; + } + return PMPI_Neighbor_alltoallv_init( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator, info, request); +} +#endif + +#if KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT_C +extern "C" int MPI_Neighbor_alltoallv_init_c( + void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Info info, + MPI_Request* request) { + if (protocol_probe::active) { + ++protocol_probe::persistent_calls; + } + return PMPI_Neighbor_alltoallv_init_c( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator, info, request); +} +#endif + +extern "C" int MPI_Start(MPI_Request* request) { + if (protocol_probe::active) { + ++protocol_probe::persistent_calls; + } + return PMPI_Start(request); +} + +extern "C" int MPI_Startall(int count, MPI_Request requests[]) { + if (protocol_probe::active) { + ++protocol_probe::persistent_calls; + } + return PMPI_Startall(count, requests); +} + +#define KAHIP_CONTRACTION_COMPLETION_WRAPPER(name, signature, arguments) \ + extern "C" int name signature { \ + if (protocol_probe::active) { \ + ++protocol_probe::completion_calls; \ + } \ + return P##name arguments; \ + } + +KAHIP_CONTRACTION_COMPLETION_WRAPPER(MPI_Test, + (MPI_Request * request, + int* complete, + MPI_Status* status), + (request, complete, status)) +KAHIP_CONTRACTION_COMPLETION_WRAPPER(MPI_Wait, + (MPI_Request * request, + MPI_Status* status), + (request, status)) +KAHIP_CONTRACTION_COMPLETION_WRAPPER(MPI_Waitall, + (int count, + MPI_Request requests[], + MPI_Status statuses[]), + (count, requests, statuses)) +KAHIP_CONTRACTION_COMPLETION_WRAPPER( + MPI_Testall, + (int count, MPI_Request requests[], int* complete, MPI_Status statuses[]), + (count, requests, complete, statuses)) +KAHIP_CONTRACTION_COMPLETION_WRAPPER(MPI_Testany, + (int count, + MPI_Request requests[], + int* index, + int* complete, + MPI_Status* status), + (count, requests, index, complete, status)) +KAHIP_CONTRACTION_COMPLETION_WRAPPER( + MPI_Testsome, + (int count, + MPI_Request requests[], + int* completed, + int indices[], + MPI_Status statuses[]), + (count, requests, completed, indices, statuses)) +KAHIP_CONTRACTION_COMPLETION_WRAPPER( + MPI_Waitany, + (int count, MPI_Request requests[], int* index, MPI_Status* status), + (count, requests, index, status)) +KAHIP_CONTRACTION_COMPLETION_WRAPPER( + MPI_Waitsome, + (int count, + MPI_Request requests[], + int* completed, + int indices[], + MPI_Status statuses[]), + (count, requests, completed, indices, statuses)) +KAHIP_CONTRACTION_COMPLETION_WRAPPER(MPI_Request_free, + (MPI_Request * request), + (request)) +KAHIP_CONTRACTION_COMPLETION_WRAPPER(MPI_Cancel, + (MPI_Request * request), + (request)) + +#undef KAHIP_CONTRACTION_COMPLETION_WRAPPER + +extern "C" int MPI_Barrier(MPI_Comm communicator) { + if (protocol_probe::active) { + ++protocol_probe::barrier_calls; + } + return PMPI_Barrier(communicator); +} + +namespace parhip { +struct parallel_contraction_test_access { + static void assign_nodes_to_cnodes( + MPI_Comm communicator, + parallel_graph_access& graph, + NodeID number_of_distinct_labels, + std::unordered_map const& label_mapping) { + parallel_contraction contraction; + contraction.get_nodes_to_cnodes_ghost_nodes( + communicator, graph, number_of_distinct_labels, label_mapping); + } + + [[nodiscard]] static auto compute_label_mapping( + MPI_Comm communicator, + parallel_graph_access& graph) + -> std::pair> { + NodeID global_num_distinct_ids = 0; + std::unordered_map label_mapping; + parallel_contraction contraction; + contraction.compute_label_mapping( + communicator, graph, global_num_distinct_ids, label_mapping); + return {global_num_distinct_ids, std::move(label_mapping)}; + } + + static void redistribute_quotient( + MPI_Comm communicator, + hashed_graph& graph, + std::unordered_map& node_weights, + NodeID number_of_cnodes, + parallel_graph_access& quotient) { + parallel_contraction contraction; + contraction.redistribute_hased_graph_and_build_graph_locally( + communicator, graph, node_weights, number_of_cnodes, quotient); + } + + static void update_ghost_weights(MPI_Comm communicator, + parallel_graph_access& graph) { + parallel_contraction contraction; + contraction.update_ghost_nodes_weights(communicator, graph); + } + + static void build_local_quotient( + MPI_Comm communicator, + parallel_graph_access& graph, + NodeID number_of_cnodes, + hashed_graph& quotient_edges, + std::unordered_map& quotient_node_weights) { + parallel_contraction contraction; + contraction.build_quotient_graph_locally(communicator, graph, + number_of_cnodes, quotient_edges, + quotient_node_weights); + } +}; +} // namespace parhip + +namespace { +void build_label_fixture(parallel_graph_access& graph, int rank, int size) { + constexpr auto global_nodes = NodeID{4}; + constexpr auto labels = std::array{3, 1, 3, 0}; + auto ranges = std::vector(static_cast(size) + 1); + for (int pe = 0; pe <= size; ++pe) { + ranges[static_cast(pe)] = + (static_cast(pe) * global_nodes) / + static_cast(size); + } + auto const first = ranges[static_cast(rank)]; + auto const end = ranges[static_cast(rank + 1)]; + auto const local_nodes = end - first; + graph.start_construction(local_nodes, 0, global_nodes, 0, false); + graph.set_range(first, local_nodes == 0 ? first : end - 1); + graph.set_range_array(ranges); + for (NodeID global = first; global < end; ++global) { + auto const node = graph.new_node(); + graph.setNodeWeight(node, 1); + graph.setNodeLabel(node, labels[static_cast(global)]); + } + graph.finish_construction(); +} + +void build_empty_label_fixture(parallel_graph_access& graph, int size) { + graph.start_construction(0, 0, 0, 0, false); + graph.set_range(0, 0); + auto ranges = std::vector(static_cast(size) + 1, 0); + graph.set_range_array(ranges); + graph.finish_construction(); +} + +void build_empty_label_fixture_with_global_count( + parallel_graph_access& graph, + NodeID global_nodes, + int size) { + graph.start_construction(0, 0, global_nodes, 0, false); + graph.set_range(0, 0); + auto ranges = std::vector(static_cast(size) + 1, 0); + graph.set_range_array(ranges); + graph.finish_construction(); +} + +struct cnode_fixture { + std::vector ranges; + std::vector> adjacency; +}; + +[[nodiscard]] auto contraction_cnode_fixture(int size) -> cnode_fixture { + if (size == 1) { + return {{0, 0}, {}}; + } + if (size == 2) { + return {{0, 1, 2}, {{1, 1}, {0, 0}}}; + } + if (size == 3) { + return {{0, 1, 2, 2}, {{1, 1}, {0, 0}}}; + } + if (size == 4) { + return {{0, 1, 2, 3, 4}, {{3, 1}, {0, 2}, {1, 3}, {2, 0}}}; + } + return {{0, 2, 3, 5, 6, 7}, {{1}, {0, 2}, {1, 3}, {2, 4}, {3, 5}, {4}, {}}}; +} + +void build_cnode_fixture(parallel_graph_access& graph, + cnode_fixture const& fixture, + int rank, + std::optional global_count = std::nullopt) { + auto const first = fixture.ranges.at(static_cast(rank)); + auto const end = fixture.ranges.at(static_cast(rank + 1)); + auto local_edges = std::size_t{0}; + for (auto global = first; global < end; ++global) { + local_edges += + fixture.adjacency.at(static_cast(global)).size(); + } + auto const global_edges = std::ranges::fold_left( + fixture.adjacency | std::views::transform(&std::vector::size), + std::size_t{0}, std::plus<>{}); + graph.start_construction( + end - first, static_cast(local_edges), + global_count.value_or(static_cast(fixture.adjacency.size())), + static_cast(global_edges), false); + graph.set_range(first, first == end ? first : end - 1); + auto ranges = fixture.ranges; + graph.set_range_array(ranges); + for (auto global = first; global < end; ++global) { + auto const local = graph.new_node(); + graph.setNodeWeight(local, 1); + graph.setNodeLabel(local, global); + for (auto const target : + fixture.adjacency.at(static_cast(global))) { + auto const edge = graph.new_edge(local, target); + graph.setEdgeWeight(edge, 1); + } + } + graph.finish_construction(); +} + +void build_local_aggregation_fixture( + parallel_graph_access& graph, + int rank, + int size, + std::array node_weights, + std::array parallel_edge_weights, + std::array coarse_nodes) { + auto ranges = + std::vector(static_cast(size) + 1, NodeID{2}); + ranges[0] = 0; + auto const local_nodes = rank == 0 ? NodeID{2} : NodeID{0}; + auto const local_edges = rank == 0 ? EdgeID{2} : EdgeID{0}; + graph.start_construction(local_nodes, local_edges, NodeID{2}, EdgeID{2}, + false); + graph.set_range(rank == 0 ? NodeID{0} : NodeID{2}, + rank == 0 ? NodeID{1} : NodeID{2}); + graph.set_range_array(ranges); + if (rank == 0) { + auto const first = graph.new_node(); + graph.setNodeWeight(first, node_weights[0]); + graph.setNodeLabel(first, 0); + for (auto const weight : parallel_edge_weights) { + auto const edge = graph.new_edge(first, NodeID{1}); + graph.setEdgeWeight(edge, weight); + } + auto const second = graph.new_node(); + graph.setNodeWeight(second, node_weights[1]); + graph.setNodeLabel(second, 1); + } + graph.finish_construction(); + graph.allocate_node_to_cnode(); + if (rank == 0) { + graph.setCNode(NodeID{0}, coarse_nodes[0]); + graph.setCNode(NodeID{1}, coarse_nodes[1]); + } +} + +[[nodiscard]] auto paired_label_mapping(cnode_fixture const& fixture) + -> std::unordered_map { + auto result = std::unordered_map{}; + for (auto global = NodeID{0}; global < fixture.adjacency.size(); ++global) { + result.emplace(global, global / NodeID{2}); + } + return result; +} + +[[nodiscard]] auto paired_coarse_count(cnode_fixture const& fixture) -> NodeID { + auto const node_count = static_cast(fixture.adjacency.size()); + return node_count == 0 ? NodeID{0} + : (node_count - NodeID{1}) / NodeID{2} + NodeID{1}; +} + +[[nodiscard]] auto snapshot_cnodes(parallel_graph_access& graph) + -> std::vector { + auto result = std::vector(graph.node_to_cnode_storage_size()); + for (auto index = std::size_t{0}; index < result.size(); ++index) { + result[index] = graph.getCNode(static_cast(index)); + } + return result; +} + +[[nodiscard]] auto snapshot_weights(parallel_graph_access& graph) + -> std::vector { + auto result = std::vector(graph.node_to_cnode_storage_size()); + for (auto index = std::size_t{0}; index < result.size(); ++index) { + result[index] = graph.getNodeWeight(static_cast(index)); + } + return result; +} + +void seed_all_weights(parallel_graph_access& graph, + NodeWeight first = NodeWeight{900}) { + for (auto index = std::size_t{0}; index < graph.node_to_cnode_storage_size(); + ++index) { + graph.setNodeWeight(static_cast(index), + first + static_cast(index)); + } +} + +void set_owner_weights(parallel_graph_access& graph, + NodeWeight first = NodeWeight{100}) { + forall_local_nodes(graph, node) { + graph.setNodeWeight(node, first + graph.getGlobalID(node)); + } + endfor +} + +[[nodiscard]] auto ghost_weights_match_owners(parallel_graph_access& graph, + NodeWeight first = NodeWeight{ + 100}) -> bool { + auto result = true; + for (auto local = graph.number_of_local_nodes() + NodeID{1}; + local < graph.number_of_local_nodes() + NodeID{1} + + graph.number_of_ghost_nodes(); + ++local) { + result = result && + graph.getNodeWeight(local) == first + graph.getGlobalID(local); + } + return result; +} + +void seed_cnodes(parallel_graph_access& graph, NodeID first = NodeID{900}) { + auto values = std::vector(graph.node_to_cnode_storage_size()); + std::ranges::iota(values, first); + graph.replace_node_to_cnode(std::move(values)); +} + +[[nodiscard]] auto contraction_validation_fixture(int size) -> cnode_fixture { + auto ranges = std::vector(static_cast(size) + 1, 6); + ranges[0] = 0; + ranges[1] = 2; + ranges[2] = 4; + ranges[3] = 6; + return {std::move(ranges), {{2}, {3}, {0, 4}, {1, 5}, {2}, {3}}}; +} + +[[nodiscard]] auto asymmetric_cnode_fixture(int size) -> cnode_fixture { + auto ranges = std::vector(static_cast(size) + 1, 2); + ranges[0] = 0; + ranges[1] = 1; + ranges[2] = 2; + return {std::move(ranges), {{1}, {}}}; +} + +void require_common_probe_result(bool local_condition) { + auto const local = local_condition ? 1 : 0; + auto common = 0; + REQUIRE(PMPI_Allreduce(&local, &common, 1, MPI_INT, MPI_MIN, + MPI_COMM_WORLD) == MPI_SUCCESS); + REQUIRE(common == 1); +} + +template +void require_collective_validation_failure(Operation&& operation, + std::string_view expected_context, + int size) { + auto caught = 0; + auto structured = 0; + auto context_matches = 0; + try { + std::invoke(std::forward(operation)); + } catch (mpi::mpi_error const& error) { + caught = 1; + structured = 1; + context_matches = error.context().find(expected_context) != + std::string_view::npos + ? 1 + : 0; + } catch (std::exception const&) { + caught = 1; + } + + auto caught_by_all = 0; + auto structured_by_all = 0; + auto context_matches_all = 0; + REQUIRE(MPI_Allreduce( + &caught, &caught_by_all, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD) == + MPI_SUCCESS); + REQUIRE(MPI_Allreduce(&structured, + &structured_by_all, + 1, + MPI_INT, + MPI_SUM, + MPI_COMM_WORLD) == MPI_SUCCESS); + REQUIRE(MPI_Allreduce(&context_matches, + &context_matches_all, + 1, + MPI_INT, + MPI_SUM, + MPI_COMM_WORLD) == MPI_SUCCESS); + REQUIRE(caught_by_all == size); + REQUIRE(structured_by_all == size); + REQUIRE(context_matches_all == size); +} +} // namespace + +TEST_CASE("ghost CNode assignment wire datatype has exact extent", + "[unit][mpi][contraction][ghost-cnode][datatype]") { + STATIC_REQUIRE( + std::is_standard_layout_v); + STATIC_REQUIRE( + std::is_trivially_copyable_v); + auto datatype = mpi::make_mpi_datatype(); + auto lower_bound = MPI_Aint{0}; + auto extent = MPI_Aint{0}; + REQUIRE(MPI_Type_get_extent(datatype.native_handle(), &lower_bound, + &extent) == MPI_SUCCESS); + REQUIRE(lower_bound == 0); + REQUIRE(extent == + static_cast(sizeof(contraction::ghost_cnode_assignment))); +} + +TEST_CASE("ghost node weight wire datatype has exact extent", + "[unit][mpi][contraction][ghost-weight][datatype]") { + STATIC_REQUIRE(std::is_standard_layout_v); + STATIC_REQUIRE(std::is_trivially_copyable_v); + auto datatype = mpi::make_mpi_datatype(); + auto lower_bound = MPI_Aint{0}; + auto extent = MPI_Aint{0}; + REQUIRE(MPI_Type_get_extent(datatype.native_handle(), &lower_bound, + &extent) == MPI_SUCCESS); + REQUIRE(lower_bound == 0); + REQUIRE(extent == + static_cast(sizeof(contraction::ghost_node_weight))); +} + +TEST_CASE("checked quotient additions preserve the exact unsigned boundary", + "[unit][mpi][contraction][quotient][arithmetic][boundary]") { + constexpr auto node_max = std::numeric_limits::max(); + constexpr auto edge_weight_max = std::numeric_limits::max(); + constexpr auto edge_count_max = std::numeric_limits::max(); + constexpr auto sender_sequence_max = std::numeric_limits::max(); + STATIC_REQUIRE(contraction::checked_add(node_max, NodeWeight{0}) == node_max); + STATIC_REQUIRE( + !contraction::checked_add(node_max, NodeWeight{1}).has_value()); + STATIC_REQUIRE(contraction::checked_add(edge_weight_max, EdgeWeight{0}) == + edge_weight_max); + STATIC_REQUIRE( + !contraction::checked_add(edge_weight_max, EdgeWeight{1}).has_value()); + STATIC_REQUIRE(contraction::checked_add(edge_count_max, EdgeID{0}) == + edge_count_max); + STATIC_REQUIRE( + !contraction::checked_add(edge_count_max, EdgeID{1}).has_value()); + STATIC_REQUIRE(contraction::checked_local_edge_count_increment( + edge_count_max - EdgeID{1}, false) == edge_count_max); + STATIC_REQUIRE( + !contraction::checked_local_edge_count_increment(edge_count_max, false) + .has_value()); + STATIC_REQUIRE(contraction::checked_local_edge_count_increment( + edge_count_max - EdgeID{2}, true) == edge_count_max); + STATIC_REQUIRE(!contraction::checked_local_edge_count_increment( + edge_count_max - EdgeID{1}, true) + .has_value()); + STATIC_REQUIRE(contraction::checked_add(sender_sequence_max, NodeID{0}) == + sender_sequence_max); + STATIC_REQUIRE( + !contraction::checked_add(sender_sequence_max, NodeID{1}).has_value()); + constexpr auto exact_global_counts = std::array{edge_count_max, EdgeID{0}}; + constexpr auto overflowing_global_counts = + std::array{edge_count_max, EdgeID{1}}; + STATIC_REQUIRE(contraction::checked_sum(exact_global_counts) == + edge_count_max); + STATIC_REQUIRE( + !contraction::checked_sum(overflowing_global_counts).has_value()); +} + +TEST_CASE("ghost node weights use one blocking neighborhood exchange", + "[unit][mpi][contraction][ghost-weight][protocol]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE(size >= 1); + REQUIRE(size <= 5); + + auto const fixture = contraction_cnode_fixture(size); + parallel_graph_access graph{MPI_COMM_WORLD}; + build_cnode_fixture(graph, fixture, rank); + forall_local_nodes(graph, node) { + auto const global_id = graph.getGlobalID(node); + graph.setNodeWeight( + node, global_id == 0 ? NodeWeight{0} : NodeWeight{17} + global_id); + } + endfor + + mpi::trace::reset(); + mpi::trace::set_active(true); +#if KAHIP_ENABLE_MPI_TRACE + mpi::trace::append(mpi::trace::quotient_node_weight( + mpi::trace::current_hierarchy(), 777, rank, 23)); +#endif + auto const trace_before = mpi::trace::snapshot(); + + { + auto probe = protocol_probe::activation{}; + parallel_contraction_test_access::update_ghost_weights(MPI_COMM_WORLD, + graph); + + auto local_is_valid = mpi::trace::snapshot() == trace_before; + for (auto local = graph.number_of_local_nodes() + NodeID{1}; + local < graph.number_of_local_nodes() + NodeID{1} + + graph.number_of_ghost_nodes(); + ++local) { + local_is_valid = local_is_valid && + graph.getNodeWeight(local) == + (graph.getGlobalID(local) == 0 + ? NodeWeight{0} + : NodeWeight{17} + graph.getGlobalID(local)); + } + + auto const& plan = graph.ghost_plan(); + local_is_valid = local_is_valid && !protocol_probe::interposer_error && + !protocol_probe::payload_extents.overflowed() && + !protocol_probe::neighbor_sources.overflowed() && + !protocol_probe::neighbor_destinations.overflowed() && + !protocol_probe::neighbor_send_counts.overflowed() && + std::ranges::equal(protocol_probe::neighbor_sources, + plan.topology().sources()) && + std::ranges::equal(protocol_probe::neighbor_destinations, + plan.topology().destinations()) && + protocol_probe::neighbor_send_counts.size() == + plan.topology().destinations().size(); + if (protocol_probe::neighbor_send_counts.size() == + plan.topology().destinations().size()) { + for (auto index = std::size_t{0}; + index < plan.topology().destinations().size(); ++index) { + local_is_valid = + local_is_valid && + *std::next(protocol_probe::neighbor_send_counts.begin(), + static_cast(index)) == + plan.outgoing_local_nodes(index).size(); + } + } + +#if KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C + auto const payload_path_is_exact = + protocol_probe::neighbor_payload_calls == 0 && + protocol_probe::neighbor_payload_c_calls == 1; +#else + auto const payload_path_is_exact = + protocol_probe::neighbor_payload_calls == 1 && + protocol_probe::neighbor_payload_c_calls == 0; +#endif + local_is_valid = + local_is_valid && protocol_probe::topology_create_calls == 1 && + protocol_probe::neighbor_count_exchange_calls == 1 && + protocol_probe::blocking_neighbor_payload_calls() == 1 && + protocol_probe::payload_extents.size() == 1 && + *protocol_probe::payload_extents.begin() == + static_cast(sizeof(contraction::ghost_node_weight)) && + payload_path_is_exact && protocol_probe::point_to_point_calls == 0 && + protocol_probe::isend_calls == 0 && protocol_probe::probe_calls == 0 && + protocol_probe::recv_calls == 0 && + protocol_probe::immediate_neighbor_calls == 0 && + protocol_probe::persistent_calls == 0 && + protocol_probe::completion_calls == 0 && + protocol_probe::barrier_calls == 0 && + protocol_probe::dense_payload_collective_calls() == 0; + require_common_probe_result(local_is_valid); + } + mpi::trace::set_active(false); + mpi::trace::reset(); +} + +TEST_CASE("ghost weight exchange reuses a prewarmed plan and refreshes values", + "[unit][mpi][contraction][ghost-weight][reuse]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 2) { + return; + } + + auto const fixture = contraction_cnode_fixture(size); + parallel_graph_access graph{MPI_COMM_WORLD}; + build_cnode_fixture(graph, fixture, rank); + seed_all_weights(graph); + set_owner_weights(graph, NodeWeight{37}); + + auto probe = protocol_probe::activation{}; + static_cast(graph.ghost_plan()); + require_common_probe_result(protocol_probe::topology_create_calls == 1); + + protocol_probe::reset(); + parallel_contraction_test_access::update_ghost_weights(MPI_COMM_WORLD, graph); + require_common_probe_result( + ghost_weights_match_owners(graph, NodeWeight{37}) && + protocol_probe::topology_create_calls == 0 && + protocol_probe::neighbor_count_exchange_calls == 1 && + protocol_probe::blocking_neighbor_payload_calls() == 1 && + protocol_probe::point_to_point_calls == 0); + + forall_local_nodes(graph, node) { + graph.setNodeWeight(node, std::numeric_limits::max()); + } + endfor protocol_probe::reset(); + parallel_contraction_test_access::update_ghost_weights(MPI_COMM_WORLD, graph); + auto local_is_exact = + protocol_probe::topology_create_calls == 0 && + protocol_probe::neighbor_count_exchange_calls == 1 && + protocol_probe::blocking_neighbor_payload_calls() == 1 && + protocol_probe::point_to_point_calls == 0; + for (auto local = graph.number_of_local_nodes() + NodeID{1}; + local < graph.number_of_local_nodes() + NodeID{1} + + graph.number_of_ghost_nodes(); + ++local) { + local_is_exact = + local_is_exact && + graph.getNodeWeight(local) == std::numeric_limits::max(); + } + require_common_probe_result(local_is_exact); +} + +TEST_CASE("ghost weight receive failures preserve every weight and trace", + "[unit][mpi][contraction][ghost-weight][failure][transaction]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + auto const modes = + std::array{protocol_probe::receive_mutation::ghost_weight_unknown_id, + protocol_probe::receive_mutation::ghost_weight_wrong_source, + protocol_probe::receive_mutation:: + ghost_weight_duplicate_replacing_missing, + protocol_probe::receive_mutation::ghost_weight_missing_extra}; + for (auto const mode : modes) { + auto const fixture = contraction_validation_fixture(size); + parallel_graph_access graph{MPI_COMM_WORLD}; + build_cnode_fixture(graph, fixture, rank); + seed_all_weights(graph); + set_owner_weights(graph); + auto const before_weights = snapshot_weights(graph); + + mpi::trace::reset(); + mpi::trace::set_active(true); +#if KAHIP_ENABLE_MPI_TRACE + mpi::trace::append(mpi::trace::quotient_node_weight( + mpi::trace::current_hierarchy(), 777, rank, 23)); +#endif + auto const before_trace = mpi::trace::snapshot(); + + auto probe = protocol_probe::activation{mode, 1, NodeID{0}, NodeID{3}}; + require_collective_validation_failure( + [&] { + parallel_contraction_test_access::update_ghost_weights(MPI_COMM_WORLD, + graph); + }, + "contraction ghost-weight received validation failed", size); + + auto const local_fired = protocol_probe::ghost_corruption_fired ? 1 : 0; + auto fired_total = 0; + REQUIRE(PMPI_Allreduce(&local_fired, &fired_total, 1, MPI_INT, MPI_SUM, + MPI_COMM_WORLD) == MPI_SUCCESS); + require_common_probe_result( + fired_total == 1 && !protocol_probe::interposer_error && + snapshot_weights(graph) == before_weights && + mpi::trace::snapshot() == before_trace && + protocol_probe::topology_create_calls == 1 && + protocol_probe::neighbor_count_exchange_calls == 1 && + protocol_probe::blocking_neighbor_payload_calls() == 1 && + protocol_probe::point_to_point_calls == 0 && + protocol_probe::immediate_neighbor_calls == 0 && + protocol_probe::persistent_calls == 0 && + protocol_probe::completion_calls == 0 && + protocol_probe::barrier_calls == 0); + + protocol_probe::mutation = protocol_probe::receive_mutation::none; + protocol_probe::ghost_corruption_fired = false; + parallel_contraction_test_access::update_ghost_weights(MPI_COMM_WORLD, + graph); + require_common_probe_result( + ghost_weights_match_owners(graph) && + mpi::trace::snapshot() == before_trace && + protocol_probe::topology_create_calls == 1 && + protocol_probe::neighbor_count_exchange_calls == 2 && + protocol_probe::blocking_neighbor_payload_calls() == 2 && + protocol_probe::point_to_point_calls == 0); + mpi::trace::set_active(false); + mpi::trace::reset(); + } +} + +TEST_CASE("ghost weights reject similar communicators before topology", + "[unit][mpi][contraction][ghost-weight][communicator][failure]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size < 2) { + return; + } + + auto const fixture = contraction_cnode_fixture(size); + parallel_graph_access graph{MPI_COMM_WORLD}; + build_cnode_fixture(graph, fixture, rank); + seed_all_weights(graph); + auto const before = snapshot_weights(graph); + auto similar = MPI_COMM_NULL; + REQUIRE(MPI_Comm_split(MPI_COMM_WORLD, 0, size - rank, &similar) == + MPI_SUCCESS); + { + auto probe = protocol_probe::activation{}; + require_collective_validation_failure( + [&] { + parallel_contraction_test_access::update_ghost_weights(similar, + graph); + }, + "contraction ghost-weight communicator validation failed", size); + require_common_probe_result( + snapshot_weights(graph) == before && + protocol_probe::topology_create_calls == 0 && + protocol_probe::neighbor_count_exchange_calls == 0 && + protocol_probe::blocking_neighbor_payload_calls() == 0); + } + REQUIRE(MPI_Comm_free(&similar) == MPI_SUCCESS); +} + +TEST_CASE("ghost weights accept a congruent caller communicator", + "[unit][mpi][contraction][ghost-weight][communicator]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 2) { + return; + } + + auto const fixture = contraction_cnode_fixture(size); + parallel_graph_access graph{MPI_COMM_WORLD}; + build_cnode_fixture(graph, fixture, rank); + seed_all_weights(graph); + set_owner_weights(graph, NodeWeight{53}); + auto duplicate = MPI_COMM_NULL; + REQUIRE(MPI_Comm_dup(MPI_COMM_WORLD, &duplicate) == MPI_SUCCESS); + { + auto probe = protocol_probe::activation{}; + parallel_contraction_test_access::update_ghost_weights(duplicate, graph); + require_common_probe_result( + ghost_weights_match_owners(graph, NodeWeight{53}) && + protocol_probe::topology_create_calls == 1 && + protocol_probe::neighbor_count_exchange_calls == 1 && + protocol_probe::blocking_neighbor_payload_calls() == 1 && + protocol_probe::point_to_point_calls == 0); + } + REQUIRE(MPI_Comm_free(&duplicate) == MPI_SUCCESS); +} + +TEST_CASE("ghost weights agree the graph global count before topology", + "[unit][mpi][contraction][ghost-weight][domain][failure]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + auto const fixture = contraction_cnode_fixture(size); + parallel_graph_access graph{MPI_COMM_WORLD}; + auto const actual_global = static_cast(fixture.adjacency.size()); + build_cnode_fixture(graph, fixture, rank, + rank == 0 ? actual_global + NodeID{1} : actual_global); + seed_all_weights(graph); + auto const before = snapshot_weights(graph); + auto probe = protocol_probe::activation{}; + require_collective_validation_failure( + [&] { + parallel_contraction_test_access::update_ghost_weights(MPI_COMM_WORLD, + graph); + }, + "contraction ghost-weight global count agreement failed", size); + require_common_probe_result( + snapshot_weights(graph) == before && + protocol_probe::topology_create_calls == 0 && + protocol_probe::neighbor_count_exchange_calls == 0 && + protocol_probe::blocking_neighbor_payload_calls() == 0); +} + +TEST_CASE("asymmetric ghost-weight topology fails commonly before payload", + "[unit][mpi][contraction][ghost-weight][asymmetric][failure]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 2) { + return; + } + + auto const fixture = asymmetric_cnode_fixture(size); + parallel_graph_access graph{MPI_COMM_WORLD}; + build_cnode_fixture(graph, fixture, rank); + seed_all_weights(graph); + auto const before = snapshot_weights(graph); + auto probe = protocol_probe::activation{}; + require_collective_validation_failure( + [&] { + parallel_contraction_test_access::update_ghost_weights(MPI_COMM_WORLD, + graph); + }, + "ghost exchange plan semantic validation failed", size); + require_common_probe_result( + snapshot_weights(graph) == before && + protocol_probe::topology_create_calls == 1 && + protocol_probe::neighbor_count_exchange_calls == 0 && + protocol_probe::blocking_neighbor_payload_calls() == 0 && + protocol_probe::point_to_point_calls == 0); +} + +TEST_CASE("local quotient node-weight aggregation rejects exact overflow", + "[unit][mpi][contraction][quotient][arithmetic][failure]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE(size >= 1); + REQUIRE(size <= 5); + + parallel_graph_access graph{MPI_COMM_WORLD}; + build_local_aggregation_fixture( + graph, rank, size, + {std::numeric_limits::max(), NodeWeight{1}}, + {EdgeWeight{0}, EdgeWeight{0}}, {NodeID{0}, NodeID{0}}); + hashed_graph local_edges; + std::unordered_map local_node_weights; + require_collective_validation_failure( + [&] { + parallel_contraction_test_access::build_local_quotient( + MPI_COMM_WORLD, graph, NodeID{2}, local_edges, local_node_weights); + }, + "local quotient node-weight aggregation overflow", size); +} + +TEST_CASE("local quotient edge-weight aggregation rejects exact overflow", + "[unit][mpi][contraction][quotient][arithmetic][failure]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE(size >= 1); + REQUIRE(size <= 5); + + parallel_graph_access graph{MPI_COMM_WORLD}; + build_local_aggregation_fixture( + graph, rank, size, {NodeWeight{0}, NodeWeight{0}}, + {std::numeric_limits::max(), EdgeWeight{1}}, + {NodeID{0}, NodeID{1}}); + hashed_graph local_edges; + std::unordered_map local_node_weights; + require_collective_validation_failure( + [&] { + parallel_contraction_test_access::build_local_quotient( + MPI_COMM_WORLD, graph, NodeID{2}, local_edges, local_node_weights); + }, + "local quotient edge-weight aggregation overflow", size); +} + +TEST_CASE("exact maximum local quotient weights remain representable", + "[unit][mpi][contraction][quotient][arithmetic]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE(size >= 1); + REQUIRE(size <= 5); + + parallel_graph_access graph{MPI_COMM_WORLD}; + build_local_aggregation_fixture( + graph, rank, size, + {std::numeric_limits::max(), NodeWeight{0}}, + {std::numeric_limits::max(), EdgeWeight{0}}, + {NodeID{0}, NodeID{1}}); + hashed_graph local_edges; + std::unordered_map local_node_weights; + parallel_contraction_test_access::build_local_quotient( + MPI_COMM_WORLD, graph, NodeID{2}, local_edges, local_node_weights); + + auto local_is_exact = local_edges.empty() && local_node_weights.empty(); + if (rank == 0) { + auto const edge = local_edges.find(hashed_edge{NodeID{2}, 0, 1}); + local_is_exact = + edge != local_edges.end() && + edge->second.weight == std::numeric_limits::max() && + local_node_weights.at(0) == std::numeric_limits::max() && + local_node_weights.at(1) == NodeWeight{0}; + } + require_common_probe_result(local_is_exact); +} + +TEST_CASE("graph CNode storage replacement is exact and transactional", + "[unit][mpi][contraction][ghost-cnode][storage]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + auto const fixture = contraction_cnode_fixture(size); + parallel_graph_access graph{MPI_COMM_WORLD}; + build_cnode_fixture(graph, fixture, rank); + + auto replacement = std::vector(graph.node_to_cnode_storage_size()); + std::ranges::iota(replacement, NodeID{17}); + auto const expected = replacement; + graph.replace_node_to_cnode(std::move(replacement)); + + auto local_is_exact = true; + for (auto node = NodeID{0}; node < static_cast(expected.size()); + ++node) { + local_is_exact = local_is_exact && graph.getCNode(node) == expected[node]; + } + require_common_probe_result(local_is_exact); +} + +TEST_CASE("ghost CNode assignment uses one blocking neighborhood exchange", + "[unit][mpi][contraction][ghost-cnode][protocol]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE(size >= 1); + REQUIRE(size <= 5); + + auto const fixture = contraction_cnode_fixture(size); + parallel_graph_access graph{MPI_COMM_WORLD}; + build_cnode_fixture(graph, fixture, rank); + auto const mapping = paired_label_mapping(fixture); + auto const coarse_count = paired_coarse_count(fixture); + + mpi::trace::reset(); + mpi::trace::set_active(true); + KAHIP_MPI_TRACE_SET_HIERARCHY(11, 7, mpi::trace::epoch::contraction); + + { + auto probe = protocol_probe::activation{}; + parallel_contraction_test_access::assign_nodes_to_cnodes( + MPI_COMM_WORLD, graph, coarse_count, mapping); + + auto local_is_valid = true; + for (auto local = NodeID{0}; local < graph.number_of_local_nodes(); + ++local) { + local_is_valid = local_is_valid && + graph.getCNode(local) == graph.getGlobalID(local) / 2; + } + for (auto local = graph.number_of_local_nodes() + NodeID{1}; + local < graph.number_of_local_nodes() + NodeID{1} + + graph.number_of_ghost_nodes(); + ++local) { + local_is_valid = local_is_valid && + graph.getCNode(local) == graph.getGlobalID(local) / 2; + } + + auto const& plan = graph.ghost_plan(); + local_is_valid = local_is_valid && !protocol_probe::interposer_error && + !protocol_probe::payload_extents.overflowed() && + !protocol_probe::neighbor_sources.overflowed() && + !protocol_probe::neighbor_destinations.overflowed() && + !protocol_probe::neighbor_send_counts.overflowed() && + std::ranges::equal(protocol_probe::neighbor_sources, + plan.topology().sources()) && + std::ranges::equal(protocol_probe::neighbor_destinations, + plan.topology().destinations()) && + protocol_probe::neighbor_send_counts.size() == + plan.topology().destinations().size(); + if (protocol_probe::neighbor_send_counts.size() == + plan.topology().destinations().size()) { + for (std::size_t index = 0; index < plan.topology().destinations().size(); + ++index) { + local_is_valid = + local_is_valid && + *std::next(protocol_probe::neighbor_send_counts.begin(), + static_cast(index)) == + plan.outgoing_local_nodes(index).size(); + } + } + + auto const trace = mpi::trace::snapshot(); +#if KAHIP_ENABLE_MPI_TRACE + auto const trace_size_is_exact = + trace.size() == static_cast(graph.number_of_local_nodes()); + local_is_valid = local_is_valid && trace_size_is_exact; + if (trace_size_is_exact) { + for (auto local = NodeID{0}; local < graph.number_of_local_nodes(); + ++local) { + auto const expected = mpi::trace::contraction_label( + mpi::trace::current_hierarchy(), graph.getGlobalID(local), rank, + graph.getNodeLabel(local), graph.getGlobalID(local) / 2); + local_is_valid = local_is_valid && + trace[static_cast(local)] == expected; + } + } +#else + local_is_valid = local_is_valid && trace.empty(); +#endif + +#if KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C + auto const payload_path_is_exact = + protocol_probe::neighbor_payload_calls == 0 && + protocol_probe::neighbor_payload_c_calls == 1; +#else + auto const payload_path_is_exact = + protocol_probe::neighbor_payload_calls == 1 && + protocol_probe::neighbor_payload_c_calls == 0; +#endif + local_is_valid = + local_is_valid && protocol_probe::topology_create_calls == 1 && + protocol_probe::neighbor_count_exchange_calls == 1 && + protocol_probe::blocking_neighbor_payload_calls() == 1 && + protocol_probe::payload_extents.size() == 1 && + *protocol_probe::payload_extents.begin() == + static_cast( + sizeof(contraction::ghost_cnode_assignment)) && + payload_path_is_exact && protocol_probe::point_to_point_calls == 0 && + protocol_probe::isend_calls == 0 && protocol_probe::probe_calls == 0 && + protocol_probe::recv_calls == 0 && + protocol_probe::immediate_neighbor_calls == 0 && + protocol_probe::persistent_calls == 0 && + protocol_probe::completion_calls == 0 && + protocol_probe::barrier_calls == 0 && + protocol_probe::dense_payload_collective_calls() == 0; + require_common_probe_result(local_is_valid); + } + mpi::trace::set_active(false); + mpi::trace::reset(); +} + +TEST_CASE( + "ghost CNode receive failures preserve the complete mapping and trace", + "[unit][mpi][contraction][ghost-cnode][failure][transaction]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + auto const modes = std::array{ + protocol_probe::receive_mutation::ghost_cnode_unknown_id, + protocol_probe::receive_mutation::ghost_cnode_wrong_source, + protocol_probe::receive_mutation::ghost_cnode_duplicate_replacing_missing, + protocol_probe::receive_mutation::ghost_cnode_missing_extra, + protocol_probe::receive_mutation::ghost_cnode_coarse_id_out_of_domain}; + + for (auto const mode : modes) { + auto const fixture = contraction_validation_fixture(size); + auto const mapping = paired_label_mapping(fixture); + auto const coarse_count = paired_coarse_count(fixture); + parallel_graph_access graph{MPI_COMM_WORLD}; + build_cnode_fixture(graph, fixture, rank); + seed_cnodes(graph); + auto const before_cnodes = snapshot_cnodes(graph); + + mpi::trace::reset(); + mpi::trace::set_active(true); +#if KAHIP_ENABLE_MPI_TRACE + mpi::trace::append(mpi::trace::contraction_label( + mpi::trace::current_hierarchy(), 777, rank, 19, 23)); +#endif + auto const before_trace = mpi::trace::snapshot(); + + auto probe = protocol_probe::activation{mode, 1, coarse_count, NodeID{2}}; + require_collective_validation_failure( + [&] { + parallel_contraction_test_access::assign_nodes_to_cnodes( + MPI_COMM_WORLD, graph, coarse_count, mapping); + }, + "contraction ghost CNode received validation failed", size); + + auto fired = protocol_probe::ghost_corruption_fired ? 1 : 0; + auto fired_total = 0; + REQUIRE(PMPI_Allreduce(&fired, &fired_total, 1, MPI_INT, MPI_SUM, + MPI_COMM_WORLD) == MPI_SUCCESS); + auto local_failure_is_transactional = + fired_total == 1 && !protocol_probe::interposer_error && + snapshot_cnodes(graph) == before_cnodes && + mpi::trace::snapshot() == before_trace && + protocol_probe::topology_create_calls == 1 && + protocol_probe::neighbor_count_exchange_calls == 1 && + protocol_probe::blocking_neighbor_payload_calls() == 1 && + protocol_probe::point_to_point_calls == 0 && + protocol_probe::immediate_neighbor_calls == 0 && + protocol_probe::persistent_calls == 0 && + protocol_probe::completion_calls == 0; + require_common_probe_result(local_failure_is_transactional); + + protocol_probe::mutation = protocol_probe::receive_mutation::none; + protocol_probe::ghost_corruption_fired = false; + parallel_contraction_test_access::assign_nodes_to_cnodes( + MPI_COMM_WORLD, graph, coarse_count, mapping); + + auto local_retry_is_exact = + protocol_probe::topology_create_calls == 1 && + protocol_probe::neighbor_count_exchange_calls == 2 && + protocol_probe::blocking_neighbor_payload_calls() == 2 && + protocol_probe::point_to_point_calls == 0; + for (auto local = NodeID{0}; local < graph.number_of_local_nodes(); + ++local) { + local_retry_is_exact = + local_retry_is_exact && + graph.getCNode(local) == graph.getGlobalID(local) / NodeID{2}; + } + for (auto local = graph.number_of_local_nodes() + NodeID{1}; + local < graph.number_of_local_nodes() + NodeID{1} + + graph.number_of_ghost_nodes(); + ++local) { + local_retry_is_exact = + local_retry_is_exact && + graph.getCNode(local) == graph.getGlobalID(local) / NodeID{2}; + } +#if KAHIP_ENABLE_MPI_TRACE + local_retry_is_exact = + local_retry_is_exact && + mpi::trace::snapshot().size() == + before_trace.size() + + static_cast(graph.number_of_local_nodes()); +#else + local_retry_is_exact = + local_retry_is_exact && mpi::trace::snapshot().empty(); +#endif + require_common_probe_result(local_retry_is_exact); + mpi::trace::set_active(false); + mpi::trace::reset(); + } +} + +TEST_CASE("zero coarse domain with local work fails before sparse payload", + "[unit][mpi][contraction][ghost-cnode][failure][zero-domain]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + auto const fixture = contraction_cnode_fixture(size); + parallel_graph_access graph{MPI_COMM_WORLD}; + build_cnode_fixture(graph, fixture, rank); + seed_cnodes(graph); + auto const before = snapshot_cnodes(graph); + auto zero_mapping = std::unordered_map{}; + for (auto global = NodeID{0}; global < fixture.adjacency.size(); ++global) { + zero_mapping.emplace(global, NodeID{0}); + } + mpi::trace::reset(); + mpi::trace::set_active(true); +#if KAHIP_ENABLE_MPI_TRACE + mpi::trace::append(mpi::trace::contraction_label( + mpi::trace::current_hierarchy(), 778, rank, 20, 24)); +#endif + auto const before_trace = mpi::trace::snapshot(); + auto probe = protocol_probe::activation{}; + require_collective_validation_failure( + [&] { + parallel_contraction_test_access::assign_nodes_to_cnodes( + MPI_COMM_WORLD, graph, 0, zero_mapping); + }, + "contraction ghost CNode local validation failed", size); + require_common_probe_result( + snapshot_cnodes(graph) == before && + mpi::trace::snapshot() == before_trace && + protocol_probe::topology_create_calls == 0 && + protocol_probe::neighbor_count_exchange_calls == 0 && + protocol_probe::blocking_neighbor_payload_calls() == 0 && + protocol_probe::point_to_point_calls == 0); + mpi::trace::set_active(false); + mpi::trace::reset(); +} + +TEST_CASE("ghost CNode local mapping failure converges before topology", + "[unit][mpi][contraction][ghost-cnode][failure][mapping]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + auto const fixture = contraction_cnode_fixture(size); + auto mapping = paired_label_mapping(fixture); + if (rank == 0) { + mapping.erase(NodeID{0}); + } + parallel_graph_access graph{MPI_COMM_WORLD}; + build_cnode_fixture(graph, fixture, rank); + seed_cnodes(graph); + auto const before = snapshot_cnodes(graph); + auto probe = protocol_probe::activation{}; + require_collective_validation_failure( + [&] { + parallel_contraction_test_access::assign_nodes_to_cnodes( + MPI_COMM_WORLD, graph, paired_coarse_count(fixture), mapping); + }, + "contraction ghost CNode local validation failed", size); + require_common_probe_result( + snapshot_cnodes(graph) == before && + protocol_probe::topology_create_calls == 0 && + protocol_probe::neighbor_count_exchange_calls == 0 && + protocol_probe::blocking_neighbor_payload_calls() == 0); +} + +TEST_CASE("ghost CNode count agreements precede topology creation", + "[unit][mpi][contraction][ghost-cnode][failure][agreement]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + auto const fixture = contraction_cnode_fixture(size); + auto const mapping = paired_label_mapping(fixture); + SECTION("coarse count") { + parallel_graph_access graph{MPI_COMM_WORLD}; + build_cnode_fixture(graph, fixture, rank); + auto probe = protocol_probe::activation{}; + require_collective_validation_failure( + [&] { + parallel_contraction_test_access::assign_nodes_to_cnodes( + MPI_COMM_WORLD, graph, + paired_coarse_count(fixture) + (rank == 0 ? NodeID{1} : 0), + mapping); + }, + "contraction ghost CNode coarse count agreement failed", size); + require_common_probe_result( + protocol_probe::topology_create_calls == 0 && + protocol_probe::neighbor_count_exchange_calls == 0 && + protocol_probe::blocking_neighbor_payload_calls() == 0); + } + SECTION("graph global count") { + parallel_graph_access graph{MPI_COMM_WORLD}; + build_cnode_fixture(graph, fixture, rank, + static_cast(fixture.adjacency.size()) + + (rank == 0 ? NodeID{1} : NodeID{0})); + auto probe = protocol_probe::activation{}; + require_collective_validation_failure( + [&] { + parallel_contraction_test_access::assign_nodes_to_cnodes( + MPI_COMM_WORLD, graph, paired_coarse_count(fixture), mapping); + }, + "contraction ghost CNode global count agreement failed", size); + require_common_probe_result( + protocol_probe::topology_create_calls == 0 && + protocol_probe::neighbor_count_exchange_calls == 0 && + protocol_probe::blocking_neighbor_payload_calls() == 0); + } +} + +TEST_CASE("ghost CNode exchange accepts congruent communicators", + "[unit][mpi][contraction][ghost-cnode][communicator]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + auto const fixture = contraction_cnode_fixture(size); + auto const mapping = paired_label_mapping(fixture); + parallel_graph_access graph{MPI_COMM_WORLD}; + build_cnode_fixture(graph, fixture, rank); + auto congruent = MPI_COMM_NULL; + REQUIRE(MPI_Comm_dup(MPI_COMM_WORLD, &congruent) == MPI_SUCCESS); + { + auto probe = protocol_probe::activation{}; + parallel_contraction_test_access::assign_nodes_to_cnodes( + congruent, graph, paired_coarse_count(fixture), mapping); + require_common_probe_result( + protocol_probe::topology_create_calls == 1 && + protocol_probe::neighbor_count_exchange_calls == 1 && + protocol_probe::blocking_neighbor_payload_calls() == 1); + } + REQUIRE(MPI_Comm_free(&congruent) == MPI_SUCCESS); +} + +TEST_CASE("ghost CNode exchange reuses a consistency-initialized plan", + "[unit][mpi][contraction][ghost-cnode][reuse]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + auto const fixture = contraction_cnode_fixture(size); + auto const mapping = paired_label_mapping(fixture); + parallel_graph_access graph{MPI_COMM_WORLD}; + build_cnode_fixture(graph, fixture, rank); + for (auto local = graph.number_of_local_nodes() + NodeID{1}; + local < graph.number_of_local_nodes() + NodeID{1} + + graph.number_of_ghost_nodes(); + ++local) { + graph.setNodeLabel(local, graph.getGlobalID(local)); + } + auto partitioner = distributed_partitioner{}; + auto config = PPartitionConfig{}; + { + auto consistency_probe = protocol_probe::activation{}; + partitioner.check_labels(MPI_COMM_WORLD, config, graph); + require_common_probe_result( + protocol_probe::topology_create_calls == 1 && + protocol_probe::neighbor_count_exchange_calls == 1 && + protocol_probe::blocking_neighbor_payload_calls() == 1 && + protocol_probe::point_to_point_calls == 0); + } + + auto probe = protocol_probe::activation{}; + parallel_contraction_test_access::assign_nodes_to_cnodes( + MPI_COMM_WORLD, graph, paired_coarse_count(fixture), mapping); + require_common_probe_result( + protocol_probe::topology_create_calls == 0 && + protocol_probe::neighbor_count_exchange_calls == 1 && + protocol_probe::blocking_neighbor_payload_calls() == 1 && + protocol_probe::point_to_point_calls == 0 && + protocol_probe::immediate_neighbor_calls == 0 && + protocol_probe::persistent_calls == 0 && + protocol_probe::completion_calls == 0); +} + +TEST_CASE("ghost CNode exchange rejects similar communicators before topology", + "[unit][mpi][contraction][ghost-cnode][communicator][failure]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size < 2) { + return; + } + auto const fixture = contraction_cnode_fixture(size); + auto const mapping = paired_label_mapping(fixture); + parallel_graph_access graph{MPI_COMM_WORLD}; + build_cnode_fixture(graph, fixture, rank); + seed_cnodes(graph); + auto const before = snapshot_cnodes(graph); + auto similar = MPI_COMM_NULL; + REQUIRE(MPI_Comm_split(MPI_COMM_WORLD, 0, size - rank, &similar) == + MPI_SUCCESS); + { + auto probe = protocol_probe::activation{}; + require_collective_validation_failure( + [&] { + parallel_contraction_test_access::assign_nodes_to_cnodes( + similar, graph, paired_coarse_count(fixture), mapping); + }, + "contraction ghost CNode communicator validation failed", size); + require_common_probe_result( + snapshot_cnodes(graph) == before && + protocol_probe::topology_create_calls == 0 && + protocol_probe::neighbor_count_exchange_calls == 0 && + protocol_probe::blocking_neighbor_payload_calls() == 0); + } + REQUIRE(MPI_Comm_free(&similar) == MPI_SUCCESS); +} + +TEST_CASE("asymmetric ghost topology fails commonly before payload", + "[unit][mpi][contraction][ghost-cnode][failure][asymmetric]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 2) { + return; + } + auto const fixture = asymmetric_cnode_fixture(size); + auto const mapping = paired_label_mapping(fixture); + parallel_graph_access graph{MPI_COMM_WORLD}; + build_cnode_fixture(graph, fixture, rank); + seed_cnodes(graph); + auto const before = snapshot_cnodes(graph); + auto probe = protocol_probe::activation{}; + require_collective_validation_failure( + [&] { + parallel_contraction_test_access::assign_nodes_to_cnodes( + MPI_COMM_WORLD, graph, paired_coarse_count(fixture), mapping); + }, + "ghost exchange plan semantic validation failed", size); + require_common_probe_result( + snapshot_cnodes(graph) == before && + protocol_probe::topology_create_calls == 1 && + protocol_probe::neighbor_count_exchange_calls == 0 && + protocol_probe::blocking_neighbor_payload_calls() == 0 && + protocol_probe::point_to_point_calls == 0); +} + +TEST_CASE("label mapping uses explicit semantic replies and preserves contiguous IDs", + "[unit][mpi][contraction][label-mapping]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE(size >= 1); + REQUIRE(size <= 5); + + parallel_graph_access graph{MPI_COMM_WORLD}; + build_label_fixture(graph, rank, size); + + protocol_probe::reset(); + protocol_probe::active = true; + auto [global_num_distinct_ids, mapping] = + parallel_contraction_test_access::compute_label_mapping( + MPI_COMM_WORLD, graph); + protocol_probe::active = false; + + REQUIRE(global_num_distinct_ids == 3); + forall_local_nodes(graph, node) { + auto const old_label = graph.getNodeLabel(node); + REQUIRE(mapping.contains(old_label)); + auto const expected = old_label == 0 ? NodeID{0} + : old_label == 1 ? NodeID{1} + : NodeID{2}; + REQUIRE(mapping.at(old_label) == expected); + } endfor + + CAPTURE(protocol_probe::all_to_all_v_calls, + protocol_probe::all_to_all_v_c_calls, + protocol_probe::isend_calls, + protocol_probe::probe_calls, + protocol_probe::payload_extents); + REQUIRE(protocol_probe::dense_payload_collective_calls() == 2); + REQUIRE(protocol_probe::payload_extents == + std::vector{static_cast(sizeof(NodeID)), + static_cast(2 * sizeof(NodeID))}); + REQUIRE(protocol_probe::isend_calls == 0); + REQUIRE(protocol_probe::probe_calls == 0); + REQUIRE(protocol_probe::recv_calls == 0); +} + +TEST_CASE("global-zero label mapping keeps empty keyed exchanges exact", + "[unit][mpi][contraction][label-mapping][zero]") { + int size = 0; + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE(size >= 1); + REQUIRE(size <= 5); + + parallel_graph_access graph{MPI_COMM_WORLD}; + build_empty_label_fixture(graph, size); + + protocol_probe::reset(); + protocol_probe::active = true; + auto [global_num_distinct_ids, mapping] = + parallel_contraction_test_access::compute_label_mapping( + MPI_COMM_WORLD, graph); + protocol_probe::active = false; + + REQUIRE(global_num_distinct_ids == 0); + REQUIRE(mapping.empty()); + REQUIRE(protocol_probe::dense_payload_collective_calls() == 2); + REQUIRE(protocol_probe::payload_extents == + std::vector{static_cast(sizeof(NodeID)), + static_cast(2 * sizeof(NodeID))}); + REQUIRE(protocol_probe::isend_calls == 0); + REQUIRE(protocol_probe::probe_calls == 0); + REQUIRE(protocol_probe::recv_calls == 0); +} + +TEST_CASE("label mapping rejects an out-of-domain local label collectively", + "[unit][mpi][contraction][label-mapping][failure]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + parallel_graph_access graph{MPI_COMM_WORLD}; + build_label_fixture(graph, rank, size); + if (rank == 0) { + graph.setNodeLabel(0, graph.number_of_global_nodes()); + } + + require_collective_validation_failure( + [&] { + static_cast( + parallel_contraction_test_access::compute_label_mapping( + MPI_COMM_WORLD, graph)); + }, + "label request local validation", + size); +} + +TEST_CASE("label mapping rejects an empty-payload global-count mismatch collectively", + "[unit][mpi][contraction][label-mapping][failure][domain]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + parallel_graph_access graph{MPI_COMM_WORLD}; + build_empty_label_fixture_with_global_count( + graph, rank == 0 ? NodeID{2} : NodeID{3}, size); + + require_collective_validation_failure( + [&] { + static_cast( + parallel_contraction_test_access::compute_label_mapping( + MPI_COMM_WORLD, graph)); + }, + "label global node count agreement failed", + size); +} + +TEST_CASE("label request receive validation rejects a valid wrong-owner record collectively", + "[unit][mpi][contraction][label-mapping][failure][receive]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + parallel_graph_access graph{MPI_COMM_WORLD}; + build_label_fixture(graph, rank, size); + protocol_probe::reset(); + protocol_probe::scoped_receive_mutation mutation{ + protocol_probe::receive_mutation::label_request_wrong_owner, 1}; + protocol_probe::active = true; + require_collective_validation_failure( + [&] { + static_cast( + parallel_contraction_test_access::compute_label_mapping( + MPI_COMM_WORLD, graph)); + }, + "label request owner validation failed", + size); + protocol_probe::active = false; +} + +TEST_CASE("label reply receive validation rejects bad keyed correlation collectively", + "[unit][mpi][contraction][label-mapping][failure][receive]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + parallel_graph_access graph{MPI_COMM_WORLD}; + build_label_fixture(graph, rank, size); + protocol_probe::reset(); + protocol_probe::scoped_receive_mutation mutation{ + protocol_probe::receive_mutation::label_reply_bad_correlation, 2}; + protocol_probe::active = true; + require_collective_validation_failure( + [&] { + static_cast( + parallel_contraction_test_access::compute_label_mapping( + MPI_COMM_WORLD, graph)); + }, + "label reply validation failed", + size); + protocol_probe::active = false; +} + +TEST_CASE("label reply receive validation rejects an out-of-domain coarse ID collectively", + "[unit][mpi][contraction][label-mapping][failure][receive]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + parallel_graph_access graph{MPI_COMM_WORLD}; + build_label_fixture(graph, rank, size); + protocol_probe::reset(); + protocol_probe::scoped_receive_mutation mutation{ + protocol_probe::receive_mutation::label_reply_coarse_id_out_of_domain, + 2}; + protocol_probe::active = true; + require_collective_validation_failure( + [&] { + static_cast( + parallel_contraction_test_access::compute_label_mapping( + MPI_COMM_WORLD, graph)); + }, + "label reply validation failed", + size); + protocol_probe::active = false; +} + +TEST_CASE("quotient edges use one dense keyed exchange and aggregate exactly", + "[unit][mpi][contraction][quotient-edges]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE(size >= 1); + REQUIRE(size <= 5); + + constexpr auto coarse_nodes = NodeID{4}; + hashed_graph local_edges; + local_edges[hashed_edge{coarse_nodes, 0, 2}].weight += + 4 * static_cast(rank + 1); + if (rank % 2 == 0) { + local_edges[hashed_edge{coarse_nodes, 1, 2}].weight += 8; + } + std::unordered_map zero_node_weights; + if (rank == 0) { + for (auto coarse = NodeID{0}; coarse < coarse_nodes; ++coarse) { + zero_node_weights.emplace(coarse, NodeWeight{0}); + } + } + parallel_graph_access quotient{MPI_COMM_WORLD}; + + protocol_probe::reset(); + protocol_probe::active = true; + parallel_contraction_test_access::redistribute_quotient( + MPI_COMM_WORLD, local_edges, zero_node_weights, coarse_nodes, quotient); + protocol_probe::active = false; + + auto actual = std::vector>{}; + forall_local_nodes(quotient, node) { + auto const source = quotient.getGlobalID(node); + forall_out_edges(quotient, edge, node) { + actual.emplace_back(source, + quotient.getGlobalID(quotient.getEdgeTarget(edge)), + quotient.getEdgeWeight(edge)); + } endfor + } endfor + auto const expected_legacy_order = rank == 0 + ? std::vector>{ + {0, 2, static_cast(size * (size + 1))}, + {1, 2, static_cast(4 * ((size + 1) / 2))}} + : rank == 1 + ? std::vector>{ + {2, 0, static_cast(size * (size + 1))}, + {2, 1, static_cast(4 * ((size + 1) / 2))}} + : std::vector>{}; + if (size == 3) { + REQUIRE(actual == expected_legacy_order); + } + std::ranges::sort(actual); + + auto const ownership = mpi::contiguous_owner_layout{ + coarse_nodes, static_cast(size)}; + auto const first = ownership.begin(static_cast(rank)); + auto const end = ownership.end(static_cast(rank)); + auto expected = std::vector>{}; + auto const edge_0_2_weight = static_cast( + size * (size + 1)); + auto const even_contributors = static_cast((size + 1) / 2); + auto const edge_1_2_weight = EdgeWeight{4} * even_contributors; + for (auto source = first; source < end; ++source) { + if (source == 0) { + expected.emplace_back(0, 2, edge_0_2_weight); + } else if (source == 1) { + expected.emplace_back(1, 2, edge_1_2_weight); + } else if (source == 2) { + expected.emplace_back(2, 0, edge_0_2_weight); + expected.emplace_back(2, 1, edge_1_2_weight); + } + } + std::ranges::sort(expected); + REQUIRE(actual == expected); + + CAPTURE(protocol_probe::all_to_all_v_calls, + protocol_probe::all_to_all_v_c_calls, + protocol_probe::isend_tags, + protocol_probe::probe_tags, + protocol_probe::recv_tags, + protocol_probe::payload_extents); + REQUIRE(protocol_probe::payload_calls_with_extent( + static_cast(4 * sizeof(NodeID))) == 1); + REQUIRE(protocol_probe::calls_in_tag_phase( + protocol_probe::isend_tags, 7, size) == 0); + REQUIRE(protocol_probe::calls_in_tag_phase( + protocol_probe::probe_tags, 7, size) == 0); + REQUIRE(protocol_probe::calls_in_tag_phase( + protocol_probe::recv_tags, 7, size) == 0); +} + +TEST_CASE("quotient node weights use one dense keyed exchange and sum exactly", + "[unit][mpi][contraction][quotient-node-weights]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE(size >= 1); + REQUIRE(size <= 5); + + constexpr auto coarse_nodes = NodeID{4}; + hashed_graph no_edges; + std::unordered_map local_weights; + local_weights[0] = static_cast(rank + 1); + local_weights[2] = 2 * static_cast(rank + 1); + if (rank % 2 == 0) { + local_weights[1] = 5; + } + if (rank == 0) { + local_weights[3] = 0; + } + parallel_graph_access quotient{MPI_COMM_WORLD}; + + protocol_probe::reset(); + protocol_probe::active = true; + parallel_contraction_test_access::redistribute_quotient( + MPI_COMM_WORLD, + no_edges, + local_weights, + coarse_nodes, + quotient); + protocol_probe::active = false; + + auto const triangular = static_cast(size * (size + 1) / 2); + auto const expected_weights = std::array{ + triangular, + static_cast(5 * ((size + 1) / 2)), + 2 * triangular, + 0}; + forall_local_nodes(quotient, node) { + auto const global = quotient.getGlobalID(node); + REQUIRE(quotient.getNodeWeight(node) == + expected_weights.at(static_cast(global))); + } endfor + + CAPTURE(protocol_probe::all_to_all_v_calls, + protocol_probe::all_to_all_v_c_calls, + protocol_probe::isend_tags, + protocol_probe::probe_tags, + protocol_probe::recv_tags, + protocol_probe::payload_extents); + REQUIRE(protocol_probe::payload_calls_with_extent( + static_cast(2 * sizeof(NodeID))) == 1); + REQUIRE(protocol_probe::calls_in_tag_phase( + protocol_probe::isend_tags, 8, size) == 0); + REQUIRE(protocol_probe::calls_in_tag_phase( + protocol_probe::probe_tags, 8, size) == 0); + REQUIRE(protocol_probe::calls_in_tag_phase( + protocol_probe::recv_tags, 8, size) == 0); +} + +TEST_CASE("received quotient edge aggregation rejects exact overflow", + "[unit][mpi][contraction][quotient-edges][arithmetic][failure]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + constexpr auto coarse_nodes = NodeID{2}; + hashed_graph local_edges; + if (rank == 0) { + local_edges[hashed_edge{coarse_nodes, 0, 1}].weight = + std::numeric_limits::max(); + } else if (rank == 1) { + local_edges[hashed_edge{coarse_nodes, 0, 1}].weight = EdgeWeight{1}; + } + std::unordered_map zero_node_weights; + if (rank == 0) { + zero_node_weights.emplace(NodeID{0}, NodeWeight{0}); + zero_node_weights.emplace(NodeID{1}, NodeWeight{0}); + } + parallel_graph_access quotient{MPI_COMM_WORLD}; + require_collective_validation_failure( + [&] { + parallel_contraction_test_access::redistribute_quotient( + MPI_COMM_WORLD, local_edges, zero_node_weights, coarse_nodes, + quotient); + }, + "quotient received edge-weight aggregation overflow", size); + require_common_probe_result(quotient.number_of_local_nodes() == 0 && + quotient.number_of_local_edges() == 0); +} + +TEST_CASE( + "received owner node-weight aggregation rejects exact overflow before " + "assignment", + "[unit][mpi][contraction][quotient-node-weights][arithmetic][failure]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + constexpr auto coarse_nodes = NodeID{2}; + hashed_graph no_edges; + std::unordered_map local_weights; + if (rank == 0) { + local_weights.emplace(NodeID{0}, std::numeric_limits::max()); + local_weights.emplace(NodeID{1}, NodeWeight{0}); + } else if (rank == 1) { + local_weights.emplace(NodeID{0}, NodeWeight{1}); + } + parallel_graph_access quotient{MPI_COMM_WORLD}; + require_collective_validation_failure( + [&] { + parallel_contraction_test_access::redistribute_quotient( + MPI_COMM_WORLD, no_edges, local_weights, coarse_nodes, quotient); + }, + "quotient owner node-weight aggregation overflow", size); + + auto local_weights_are_uncommitted = true; + forall_local_nodes(quotient, node) { + local_weights_are_uncommitted = + local_weights_are_uncommitted && + quotient.getNodeWeight(node) == NodeWeight{0}; + } + endfor require_common_probe_result(local_weights_are_uncommitted); +} + +TEST_CASE("received quotient weights preserve an exact maximum plus zero", + "[unit][mpi][contraction][quotient][arithmetic][boundary]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + constexpr auto coarse_nodes = NodeID{2}; + hashed_graph local_edges; + if (rank == 0) { + local_edges[hashed_edge{coarse_nodes, 0, 1}].weight = + std::numeric_limits::max(); + } else if (rank == 1) { + local_edges[hashed_edge{coarse_nodes, 0, 1}].weight = EdgeWeight{0}; + } + std::unordered_map local_weights; + if (rank == 0) { + local_weights.emplace(NodeID{0}, std::numeric_limits::max()); + local_weights.emplace(NodeID{1}, NodeWeight{0}); + } else if (rank == 1) { + local_weights.emplace(NodeID{0}, NodeWeight{0}); + } + parallel_graph_access quotient{MPI_COMM_WORLD}; + parallel_contraction_test_access::redistribute_quotient( + MPI_COMM_WORLD, local_edges, local_weights, coarse_nodes, quotient); + + auto local_is_exact = true; + forall_local_nodes(quotient, node) { + auto const global_id = quotient.getGlobalID(node); + local_is_exact = + local_is_exact && + quotient.getNodeWeight(node) == + (global_id == 0 ? std::numeric_limits::max() + : NodeWeight{0}); + forall_out_edges(quotient, edge, node) { + local_is_exact = + local_is_exact && + quotient.getEdgeWeight(edge) == + std::numeric_limits::max() / EdgeWeight{2}; + } + endfor + } + endfor require_common_probe_result(local_is_exact); +} + +TEST_CASE( + "quotient owner node weights require exact local coverage", + "[unit][mpi][contraction][quotient-node-weights][coverage][failure]") { + auto size = 0; + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE(size >= 1); + REQUIRE(size <= 5); + + constexpr auto coarse_nodes = NodeID{2}; + hashed_graph no_edges; + std::unordered_map missing_weights{ + {NodeID{0}, NodeWeight{7}}}; + parallel_graph_access quotient{MPI_COMM_WORLD}; + require_collective_validation_failure( + [&] { + parallel_contraction_test_access::redistribute_quotient( + MPI_COMM_WORLD, no_edges, missing_weights, coarse_nodes, quotient); + }, + "quotient owner node-weight coverage failed", size); +} + +TEST_CASE("quotient edge receive validation rejects a valid wrong-owner source collectively", + "[unit][mpi][contraction][quotient-edges][failure][receive]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + constexpr auto coarse_nodes = NodeID{4}; + hashed_graph local_edges; + local_edges[hashed_edge{coarse_nodes, 0, 2}].weight = 4; + std::unordered_map no_node_weights; + parallel_graph_access quotient{MPI_COMM_WORLD}; + protocol_probe::reset(); + protocol_probe::scoped_receive_mutation mutation{ + protocol_probe::receive_mutation::quotient_edge_wrong_owner, 1}; + protocol_probe::active = true; + require_collective_validation_failure( + [&] { + parallel_contraction_test_access::redistribute_quotient( + MPI_COMM_WORLD, + local_edges, + no_node_weights, + coarse_nodes, + quotient); + }, + "quotient edge received validation failed", + size); + protocol_probe::active = false; +} + +TEST_CASE("quotient edge receive validation rejects an out-of-domain target collectively", + "[unit][mpi][contraction][quotient-edges][failure][receive]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + constexpr auto coarse_nodes = NodeID{4}; + hashed_graph local_edges; + local_edges[hashed_edge{coarse_nodes, 0, 2}].weight = 4; + std::unordered_map no_node_weights; + parallel_graph_access quotient{MPI_COMM_WORLD}; + protocol_probe::reset(); + protocol_probe::scoped_receive_mutation mutation{ + protocol_probe::receive_mutation::quotient_edge_target_out_of_domain, + 1}; + protocol_probe::active = true; + require_collective_validation_failure( + [&] { + parallel_contraction_test_access::redistribute_quotient( + MPI_COMM_WORLD, + local_edges, + no_node_weights, + coarse_nodes, + quotient); + }, + "quotient edge received validation failed", + size); + protocol_probe::active = false; +} + +TEST_CASE("quotient edge receive validation rejects a sender-sequence gap collectively", + "[unit][mpi][contraction][quotient-edges][failure][receive]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + constexpr auto coarse_nodes = NodeID{4}; + hashed_graph local_edges; + local_edges[hashed_edge{coarse_nodes, 0, 2}].weight = 4; + std::unordered_map no_node_weights; + parallel_graph_access quotient{MPI_COMM_WORLD}; + protocol_probe::reset(); + protocol_probe::scoped_receive_mutation mutation{ + protocol_probe::receive_mutation::quotient_edge_sequence_gap, 1}; + protocol_probe::active = true; + require_collective_validation_failure( + [&] { + parallel_contraction_test_access::redistribute_quotient( + MPI_COMM_WORLD, + local_edges, + no_node_weights, + coarse_nodes, + quotient); + }, + "quotient edge received validation failed", + size); + protocol_probe::active = false; +} + +TEST_CASE("quotient node-weight receive validation rejects a valid wrong-owner ID collectively", + "[unit][mpi][contraction][quotient-node-weights][failure][receive]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + constexpr auto coarse_nodes = NodeID{4}; + hashed_graph no_edges; + std::unordered_map local_weights{{0, 1}}; + parallel_graph_access quotient{MPI_COMM_WORLD}; + protocol_probe::reset(); + protocol_probe::scoped_receive_mutation mutation{ + protocol_probe::receive_mutation::quotient_node_weight_wrong_owner, 2}; + protocol_probe::active = true; + require_collective_validation_failure( + [&] { + parallel_contraction_test_access::redistribute_quotient( + MPI_COMM_WORLD, + no_edges, + local_weights, + coarse_nodes, + quotient); + }, + "quotient node-weight received validation failed", + size); + protocol_probe::active = false; +} + +TEST_CASE("zero coarse-node redistribution remains an empty dense exchange", + "[unit][mpi][contraction][quotient][zero]") { + int size = 0; + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE(size >= 1); + REQUIRE(size <= 5); + + hashed_graph no_edges; + std::unordered_map no_node_weights; + parallel_graph_access quotient{MPI_COMM_WORLD}; + + protocol_probe::reset(); + protocol_probe::active = true; + parallel_contraction_test_access::redistribute_quotient( + MPI_COMM_WORLD, no_edges, no_node_weights, 0, quotient); + protocol_probe::active = false; + + REQUIRE(quotient.number_of_local_nodes() == 0); + REQUIRE(quotient.number_of_local_edges() == 0); + REQUIRE(quotient.get_from_range() == 0); + REQUIRE(quotient.get_to_range() == 0); + REQUIRE(protocol_probe::dense_payload_collective_calls() == 2); +} + +TEST_CASE("quotient redistribution rejects an empty-payload coarse-count mismatch collectively", + "[unit][mpi][contraction][quotient][failure][domain]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + hashed_graph no_edges; + std::unordered_map no_node_weights; + parallel_graph_access quotient{MPI_COMM_WORLD}; + + require_collective_validation_failure( + [&] { + parallel_contraction_test_access::redistribute_quotient( + MPI_COMM_WORLD, + no_edges, + no_node_weights, + rank == 0 ? NodeID{2} : NodeID{3}, + quotient); + }, + "quotient coarse node count agreement failed", + size); + REQUIRE(quotient.number_of_local_nodes() == 0); +} + +TEST_CASE("quotient redistribution rejects a tail-padding edge source collectively", + "[unit][mpi][contraction][quotient-edges][failure]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + constexpr auto coarse_nodes = NodeID{4}; + hashed_graph local_edges; + if (rank == 0) { + local_edges[hashed_edge{coarse_nodes, coarse_nodes, 0}].weight = 4; + } + std::unordered_map no_node_weights; + parallel_graph_access quotient{MPI_COMM_WORLD}; + + require_collective_validation_failure( + [&] { + parallel_contraction_test_access::redistribute_quotient( + MPI_COMM_WORLD, + local_edges, + no_node_weights, + coarse_nodes, + quotient); + }, + "quotient edge local validation", + size); + REQUIRE(quotient.number_of_local_nodes() == 0); + REQUIRE(quotient.number_of_local_edges() == 0); +} + +TEST_CASE("quotient redistribution rejects a tail-padding edge target collectively", + "[unit][mpi][contraction][quotient-edges][failure]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + constexpr auto coarse_nodes = NodeID{4}; + hashed_graph local_edges; + if (rank == 0) { + local_edges[hashed_edge{coarse_nodes, 0, coarse_nodes}].weight = 4; + } + std::unordered_map no_node_weights; + parallel_graph_access quotient{MPI_COMM_WORLD}; + + require_collective_validation_failure( + [&] { + parallel_contraction_test_access::redistribute_quotient( + MPI_COMM_WORLD, + local_edges, + no_node_weights, + coarse_nodes, + quotient); + }, + "quotient edge local validation", + size); + REQUIRE(quotient.number_of_local_nodes() == 0); + REQUIRE(quotient.number_of_local_edges() == 0); +} + +TEST_CASE("quotient redistribution rejects a tail-padding node weight collectively", + "[unit][mpi][contraction][quotient-node-weights][failure]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + constexpr auto coarse_nodes = NodeID{4}; + hashed_graph no_edges; + std::unordered_map local_weights; + if (rank == 0) { + local_weights[coarse_nodes] = 7; + } + parallel_graph_access quotient{MPI_COMM_WORLD}; + + require_collective_validation_failure( + [&] { + parallel_contraction_test_access::redistribute_quotient( + MPI_COMM_WORLD, + no_edges, + local_weights, + coarse_nodes, + quotient); + }, + "quotient node-weight local validation", + size); + REQUIRE(quotient.number_of_local_nodes() == 0); + REQUIRE(quotient.number_of_local_edges() == 0); +} + +TEST_CASE("all to all vector of vectors", "[unit][mpi]") { + SECTION("empty cases") { + PEID rank, size; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + const std::vector> v_empty( + static_cast(size), std::vector{1, 2, 3}); + auto vec = mpi::all_to_all(v_empty, MPI_COMM_WORLD); + MPI_Barrier(MPI_COMM_WORLD); + REQUIRE(v_empty == vec); + } + SECTION("complex case") { + PEID rank, size; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + auto v_empty = std::vector>( + static_cast(size)); + for (int destination = 0; destination < size; ++destination) { + v_empty[static_cast(destination)].assign( + static_cast(destination), + static_cast(destination)); + } + auto vec = mpi::all_to_all(v_empty, MPI_COMM_WORLD); + MPI_Barrier(MPI_COMM_WORLD); + std::cout << "rank: " << rank << " -> "; + write_nested_range(std::cout, vec); + std::cout << '\n'; + REQUIRE(v_empty.size() == vec.size()); + } + + SECTION("custom types") { + PEID rank, size; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + const std::vector> empty_edges( + static_cast(size), + std::vector{{0, 0, 0, 0}}); + const std::vector> empty_weights( + static_cast(size), + std::vector{{}}); + const auto empty_meta = empty_weights; + auto vec_1 = mpi::all_to_all(empty_edges, MPI_COMM_WORLD); + auto vec_2 = mpi::all_to_all(empty_weights, MPI_COMM_WORLD); + auto vec_3 = mpi::all_to_all(empty_meta, MPI_COMM_WORLD); + MPI_Barrier(MPI_COMM_WORLD); + REQUIRE(empty_edges.size() == vec_1.size()); + REQUIRE(empty_weights.size() == vec_2.size()); + REQUIRE(empty_meta.size() == vec_3.size()); + } +} diff --git a/parallel/parallel_src/tests/parallel_contraction/parallel_contraction_test.cpp b/parallel/parallel_src/tests/parallel_contraction/parallel_contraction_test.cpp new file mode 100644 index 00000000..d3b95257 --- /dev/null +++ b/parallel/parallel_src/tests/parallel_contraction/parallel_contraction_test.cpp @@ -0,0 +1,155 @@ +// +// Created by Erich Essmann on 16/08/2024. +// +#include +#include +#include +#include +#include + +#include "parallel_contraction_projection/parallel_contraction.h" +#include "tools/timer.h" + +#include "communication/mpi_tools.h" +using namespace parhip; + +TEST_CASE("flattening vector of messages", "[unit][mpi]") { + SECTION("Empty Vector") { + std::vector > m_empty{}; + auto [flattened, offsets, lengths] = mpi::pack_messages(m_empty); + REQUIRE(flattened.empty()); + REQUIRE(offsets.empty()); + REQUIRE(lengths.empty()); + + std::vector > m_empty2{{}}; + auto [flattened2, offsets2, lengths2] = mpi::pack_messages(m_empty2); + REQUIRE(flattened2.empty()); + REQUIRE(offsets2.size() == 1); + REQUIRE(lengths2.size() == 1); + } + SECTION("Simple Vector") { + std::vector > m_simple{{1, 2, 3, 4}}; + auto [flattened, offsets, lengths] = mpi::pack_messages(m_simple); + + // Testing sizes + REQUIRE(flattened.size() == 4); + REQUIRE(offsets.size() == 1); + REQUIRE(lengths.size() == 1); + + // Testing content + REQUIRE(flattened == m_simple.at(0)); + } + + SECTION("Complex Vector") { + std::vector > data = { + {1, 2, 3}, {}, {4, 5}, {6, 7, 8, 9}, {}}; + + auto [flattened, offsets, lengths] = mpi::pack_messages(data); + + // Testing sizes + REQUIRE(flattened.size() == 9); + REQUIRE(offsets.size() == 5); + REQUIRE(lengths.size() == 5); + + // Creating Subspans + std::vector s1, s2, s3, s4, s5; + s1.insert(s1.begin(), flattened.begin() + offsets[0], + flattened.begin() + offsets[0] + lengths[0]); + s2.insert(s2.begin(), flattened.begin() + offsets[1], + flattened.begin() + offsets[1] + lengths[1]); + s3.insert(s3.begin(), flattened.begin() + offsets[2], + flattened.begin() + offsets[2] + lengths[2]); + s4.insert(s4.begin(), flattened.begin() + offsets[3], + flattened.begin() + offsets[3] + lengths[3]); + s5.insert(s5.begin(), flattened.begin() + offsets[4], + flattened.begin() + offsets[4] + lengths[4]); + + REQUIRE(s1 == data[0]); + REQUIRE(s2 == data[1]); + REQUIRE(s3 == data[2]); + REQUIRE(s4 == data[3]); + REQUIRE(s5 == data[4]); + } +} + +TEST_CASE("Packing and Unpacking for messages", "[unit][mpi]") { + SECTION("Empty Vector") { + const std::vector > m_empty{}; + auto const packed = mpi::pack_messages(m_empty); + auto const unpacked = mpi::unpack_messages(packed); + + REQUIRE(m_empty == unpacked); + } + + SECTION("Message of an empty Vector") { + std::vector > const m_empty{{}}; + auto const packed = mpi::pack_messages(m_empty); + auto const unpacked = mpi::unpack_messages(packed); + + REQUIRE(m_empty == unpacked); + } + + SECTION("Complex Message") { + std::vector > data = {{1, 2, 3}, {}, {4, 5}, + {}, {}, {6, 7, 8, 9}}; + auto const packed = mpi::pack_messages(data); + auto const unpacked = mpi::unpack_messages(packed); + REQUIRE(data == unpacked); + } +} + +using mpi_native_types = std::tuple; +static auto const mpi_datatypes = std::array{ + MPI_CHAR, MPI_WCHAR, MPI_SIGNED_CHAR, MPI_UNSIGNED_CHAR, + MPI_SHORT, MPI_UNSIGNED_SHORT, MPI_INT, MPI_UNSIGNED, + MPI_LONG, MPI_UNSIGNED_LONG, MPI_LONG_LONG_INT, MPI_UNSIGNED_LONG_LONG, + MPI_FLOAT, MPI_DOUBLE, MPI_LONG_DOUBLE, MPI_CXX_BOOL}; + +struct MyTestType { + int a; + float b; + char c; + double d; + long double e; + long long f; +}; +TEMPLATE_LIST_TEST_CASE("MPI Native Datatype mapping", + "[unit][mpi]", + mpi_native_types) { + SECTION("Native MPI data kinds") { + STATIC_REQUIRE(mpi::mpi_native_datatype); + } + SECTION("Native MPI data type") { + auto matches = + std::ranges::views::transform(mpi_datatypes, [](auto datatype) -> bool { + return (datatype == mpi::get_mpi_datatype()); + }); + auto any_match = std::accumulate(std::begin(matches), std::end(matches), + false, std::logical_or()); + REQUIRE(any_match == true); + } +} diff --git a/parallel/parallel_src/tests/parallel_projection/parallel_block_down_mpi_test.cpp b/parallel/parallel_src/tests/parallel_projection/parallel_block_down_mpi_test.cpp new file mode 100644 index 00000000..f8f3787a --- /dev/null +++ b/parallel/parallel_src/tests/parallel_projection/parallel_block_down_mpi_test.cpp @@ -0,0 +1,1919 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "communication/contiguous_owner_layout.h" +#include "communication/ghost_exchange_plan.h" +#include "communication/mpi_adapter.h" +#include "communication/mpi_trace.h" +#include "data_structure/parallel_graph_access.h" +#include "kahip_mpi_capabilities.h" +#include "parallel_contraction_projection/parallel_block_down_propagation.h" + +namespace protocol_probe { +template +class fixed_log final { + public: + void clear() noexcept { + size_ = 0; + overflowed_ = false; + } + + void push_back(T value) noexcept { + if (size_ == Capacity) { + overflowed_ = true; + return; + } + values_[size_++] = value; + } + + [[nodiscard]] auto begin() const noexcept { return values_.begin(); } + [[nodiscard]] auto end() const noexcept { + return values_.begin() + static_cast(size_); + } + [[nodiscard]] auto size() const noexcept -> std::size_t { return size_; } + [[nodiscard]] auto overflowed() const noexcept -> bool { return overflowed_; } + + friend auto operator==(fixed_log const& lhs, std::vector const& rhs) + -> bool { + return std::ranges::equal(lhs, rhs); + } + + private: + std::array values_{}; + std::size_t size_ = 0; + bool overflowed_ = false; +}; + +inline bool active = false; +inline bool interposer_error = false; +inline int dense_payload_calls = 0; +inline int dense_payload_c_calls = 0; +inline int topology_create_calls = 0; +inline int neighbor_count_calls = 0; +inline int neighbor_payload_calls = 0; +inline int neighbor_payload_c_calls = 0; +inline int point_to_point_calls = 0; +inline int immediate_neighbor_calls = 0; +inline int persistent_calls = 0; +inline int completion_calls = 0; +inline int barrier_calls = 0; +inline int tag11_isend_calls = 0; +inline int tag11_probe_calls = 0; +inline int tag11_recv_calls = 0; +inline fixed_log dense_payload_extents; +inline fixed_log neighbor_payload_extents; +inline fixed_log neighbor_sources; +inline fixed_log neighbor_destinations; +inline fixed_log neighbor_send_counts; +inline bool corruption_fired = false; + +enum class receive_mutation { + none, + dense_unknown_id, + dense_block_equal_k, + dense_conflicting_duplicate, + dense_duplicate_replacing_missing, + neighbor_unknown_id, + neighbor_wrong_source, + neighbor_duplicate_replacing_missing, + neighbor_missing_extra, + neighbor_block_equal_k, +}; + +inline receive_mutation mutation = receive_mutation::none; +inline int mutation_target_rank = 0; +inline parhip::PartitionID mutation_block_domain = 0; +inline parhip::NodeID mutation_replacement_id = 0; + +void reset() noexcept { + interposer_error = false; + dense_payload_calls = 0; + dense_payload_c_calls = 0; + topology_create_calls = 0; + neighbor_count_calls = 0; + neighbor_payload_calls = 0; + neighbor_payload_c_calls = 0; + point_to_point_calls = 0; + immediate_neighbor_calls = 0; + persistent_calls = 0; + completion_calls = 0; + barrier_calls = 0; + tag11_isend_calls = 0; + tag11_probe_calls = 0; + tag11_recv_calls = 0; + dense_payload_extents.clear(); + neighbor_payload_extents.clear(); + neighbor_sources.clear(); + neighbor_destinations.clear(); + neighbor_send_counts.clear(); + corruption_fired = false; + mutation = receive_mutation::none; + mutation_target_rank = 0; + mutation_block_domain = 0; + mutation_replacement_id = 0; +} + +void record_extent(MPI_Datatype datatype, + fixed_log& extents) noexcept { + auto lower_bound = MPI_Aint{0}; + auto extent = MPI_Aint{0}; + if (PMPI_Type_get_extent(datatype, &lower_bound, &extent) != MPI_SUCCESS || + lower_bound != 0) { + interposer_error = true; + return; + } + extents.push_back(extent); +} + +void record_tag11(int tag, MPI_Comm communicator, int& counter) noexcept { + auto size = 0; + if (PMPI_Comm_size(communicator, &size) != MPI_SUCCESS || size <= 0) { + interposer_error = true; + return; + } + if (11 * size <= tag && tag < 12 * size) { + ++counter; + } +} + +class activation final { + public: + explicit activation(receive_mutation selected = receive_mutation::none, + int target_rank = 0, + parhip::PartitionID block_domain = 0, + parhip::NodeID replacement_id = 0) noexcept { + reset(); + mutation = selected; + mutation_target_rank = target_rank; + mutation_block_domain = block_domain; + mutation_replacement_id = replacement_id; + active = true; + } + ~activation() noexcept { active = false; } + + activation(activation const&) = delete; + auto operator=(activation const&) -> activation& = delete; +}; + +void record_graph_neighbors(MPI_Comm communicator) noexcept { + auto indegree = 0; + auto outdegree = 0; + auto weighted = 0; + if (PMPI_Dist_graph_neighbors_count(communicator, &indegree, &outdegree, + &weighted) != MPI_SUCCESS || + indegree < 0 || outdegree < 0 || indegree > 32 || outdegree > 32) { + interposer_error = true; + return; + } + auto sources = std::array{}; + auto destinations = std::array{}; + if (PMPI_Dist_graph_neighbors(communicator, indegree, sources.data(), + MPI_UNWEIGHTED, outdegree, destinations.data(), + MPI_UNWEIGHTED) != MPI_SUCCESS) { + interposer_error = true; + return; + } + for (auto index = 0; index < indegree; ++index) { + neighbor_sources.push_back(sources[static_cast(index)]); + } + for (auto index = 0; index < outdegree; ++index) { + neighbor_destinations.push_back( + destinations[static_cast(index)]); + } +} + +template +void record_neighbor_send_counts(Count const counts[], + MPI_Comm communicator) noexcept { + auto indegree = 0; + auto outdegree = 0; + auto weighted = 0; + if (PMPI_Dist_graph_neighbors_count(communicator, &indegree, &outdegree, + &weighted) != MPI_SUCCESS || + outdegree < 0 || outdegree > 32 || + (outdegree != 0 && counts == nullptr)) { + interposer_error = true; + return; + } + for (auto index = 0; index < outdegree; ++index) { + if (!std::in_range(counts[index])) { + interposer_error = true; + return; + } + neighbor_send_counts.push_back( + static_cast(counts[index])); + } +} + +[[nodiscard]] auto is_dense_mutation() noexcept -> bool { + return mutation == receive_mutation::dense_unknown_id || + mutation == receive_mutation::dense_block_equal_k || + mutation == receive_mutation::dense_conflicting_duplicate || + mutation == receive_mutation::dense_duplicate_replacing_missing; +} + +[[nodiscard]] auto is_neighbor_mutation() noexcept -> bool { + return mutation == receive_mutation::neighbor_unknown_id || + mutation == receive_mutation::neighbor_wrong_source || + mutation == receive_mutation::neighbor_duplicate_replacing_missing || + mutation == receive_mutation::neighbor_missing_extra || + mutation == receive_mutation::neighbor_block_equal_k; +} + +template +void mutate_dense_payload(void* receive_buffer, + Count const receive_counts[], + Displacement const receive_displacements[], + MPI_Datatype datatype, + MPI_Comm communicator) noexcept { + if (!active || !is_dense_mutation()) { + return; + } + auto rank = 0; + auto size = 0; + auto lower_bound = MPI_Aint{0}; + auto extent = MPI_Aint{0}; + if (PMPI_Comm_rank(communicator, &rank) != MPI_SUCCESS || + PMPI_Comm_size(communicator, &size) != MPI_SUCCESS || size < 0 || + PMPI_Type_get_extent(datatype, &lower_bound, &extent) != MPI_SUCCESS || + lower_bound != 0 || + extent != + static_cast(sizeof(parhip::block_down::block_update))) { + interposer_error = true; + return; + } + if (rank != mutation_target_rank) { + return; + } + if (size != 0 && (receive_buffer == nullptr || receive_counts == nullptr || + receive_displacements == nullptr)) { + interposer_error = true; + return; + } + auto* records = + static_cast(receive_buffer); + auto first_source = -1; + auto first_index = std::size_t{0}; + for (auto source = 0; source < size; ++source) { + if (receive_counts[source] <= 0 || + !std::in_range(receive_displacements[source])) { + continue; + } + first_source = source; + first_index = static_cast(receive_displacements[source]); + break; + } + if (first_source < 0) { + interposer_error = true; + return; + } + switch (mutation) { + case receive_mutation::dense_unknown_id: + records[first_index].coarse_global_id = + std::numeric_limits::max(); + corruption_fired = true; + return; + case receive_mutation::dense_block_equal_k: + records[first_index].block = mutation_block_domain; + corruption_fired = true; + return; + case receive_mutation::dense_conflicting_duplicate: + for (auto source = first_source; source < size; ++source) { + if (receive_counts[source] <= 0 || + !std::in_range(receive_displacements[source])) { + continue; + } + auto const offset = + static_cast(receive_displacements[source]); + for (auto index = std::size_t{0}; + index < static_cast(receive_counts[source]); + ++index) { + auto& candidate = records[offset + index]; + if (offset + index != first_index && + candidate.coarse_global_id == + records[first_index].coarse_global_id) { + candidate.block = records[first_index].block == 0 + ? parhip::PartitionID{1} + : records[first_index].block - 1; + corruption_fired = true; + return; + } + } + } + interposer_error = true; + return; + case receive_mutation::dense_duplicate_replacing_missing: + for (auto source = first_source; source < size; ++source) { + if (receive_counts[source] <= 0 || + !std::in_range(receive_displacements[source])) { + continue; + } + auto const offset = + static_cast(receive_displacements[source]); + for (auto index = std::size_t{0}; + index < static_cast(receive_counts[source]); + ++index) { + if (offset + index == first_index) { + continue; + } + records[offset + index].coarse_global_id = + records[first_index].coarse_global_id; + records[offset + index].block = records[first_index].block; + corruption_fired = true; + return; + } + } + interposer_error = true; + return; + default: + interposer_error = true; + return; + } +} + +template +void mutate_neighbor_payload(void* receive_buffer, + Count const receive_counts[], + Displacement const receive_displacements[], + MPI_Datatype datatype, + MPI_Comm communicator) noexcept { + if (!active || !is_neighbor_mutation()) { + return; + } + auto rank = 0; + auto indegree = 0; + auto outdegree = 0; + auto weighted = 0; + auto lower_bound = MPI_Aint{0}; + auto extent = MPI_Aint{0}; + if (PMPI_Comm_rank(communicator, &rank) != MPI_SUCCESS || + PMPI_Dist_graph_neighbors_count(communicator, &indegree, &outdegree, + &weighted) != MPI_SUCCESS || + indegree < 0 || indegree > 32 || + PMPI_Type_get_extent(datatype, &lower_bound, &extent) != MPI_SUCCESS || + lower_bound != 0 || + extent != + static_cast(sizeof(parhip::block_down::block_update)) || + (indegree != 0 && + (receive_buffer == nullptr || receive_counts == nullptr || + receive_displacements == nullptr))) { + interposer_error = true; + return; + } + if (rank != mutation_target_rank) { + return; + } + auto nonempty = std::array{}; + auto nonempty_count = std::size_t{0}; + for (auto index = 0; index < indegree; ++index) { + if (receive_counts[index] > 0) { + nonempty[nonempty_count++] = index; + } + } + if (nonempty_count == 0 || + !std::in_range(receive_displacements[nonempty[0]])) { + interposer_error = true; + return; + } + auto* records = + static_cast(receive_buffer); + auto const first = nonempty[0]; + auto const first_offset = + static_cast(receive_displacements[first]); + switch (mutation) { + case receive_mutation::neighbor_unknown_id: + records[first_offset].coarse_global_id = + std::numeric_limits::max(); + corruption_fired = true; + return; + case receive_mutation::neighbor_wrong_source: + if (nonempty_count < 2 || + !std::in_range(receive_displacements[nonempty[1]])) { + interposer_error = true; + return; + } + records[first_offset] = + records[static_cast(receive_displacements[nonempty[1]])]; + corruption_fired = true; + return; + case receive_mutation::neighbor_duplicate_replacing_missing: + if (receive_counts[first] < 2) { + interposer_error = true; + return; + } + records[first_offset + 1] = records[first_offset]; + corruption_fired = true; + return; + case receive_mutation::neighbor_missing_extra: + records[first_offset].coarse_global_id = mutation_replacement_id; + corruption_fired = true; + return; + case receive_mutation::neighbor_block_equal_k: + records[first_offset].block = mutation_block_domain; + corruption_fired = true; + return; + default: + interposer_error = true; + return; + } +} +} // namespace protocol_probe + +extern "C" int MPI_Alltoallv(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (protocol_probe::active) { + ++protocol_probe::dense_payload_calls; + protocol_probe::record_extent(send_datatype, + protocol_probe::dense_payload_extents); + } + auto const result = + PMPI_Alltoallv(send_buffer, send_counts, send_displacements, + send_datatype, receive_buffer, receive_counts, + receive_displacements, receive_datatype, communicator); + if (protocol_probe::active && result == MPI_SUCCESS) { + protocol_probe::mutate_dense_payload(receive_buffer, receive_counts, + receive_displacements, + receive_datatype, communicator); + } + return result; +} + +#if KAHIP_HAVE_MPI_ALLTOALLV_C +extern "C" int MPI_Alltoallv_c(void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (protocol_probe::active) { + ++protocol_probe::dense_payload_c_calls; + protocol_probe::record_extent(send_datatype, + protocol_probe::dense_payload_extents); + } + auto const result = + PMPI_Alltoallv_c(send_buffer, send_counts, send_displacements, + send_datatype, receive_buffer, receive_counts, + receive_displacements, receive_datatype, communicator); + if (protocol_probe::active && result == MPI_SUCCESS) { + protocol_probe::mutate_dense_payload(receive_buffer, receive_counts, + receive_displacements, + receive_datatype, communicator); + } + return result; +} +#endif + +extern "C" int MPI_Dist_graph_create(MPI_Comm communicator, + int source_count, + int const sources[], + int const degrees[], + int const destinations[], + int const weights[], + MPI_Info info, + int reorder, + MPI_Comm* graph_communicator) { + if (protocol_probe::active) { + ++protocol_probe::topology_create_calls; + if (reorder != 0) { + protocol_probe::interposer_error = true; + } + } + return PMPI_Dist_graph_create(communicator, source_count, sources, degrees, + destinations, weights, info, reorder, + graph_communicator); +} + +extern "C" int MPI_Neighbor_alltoall(void const* send_buffer, + int send_count, + MPI_Datatype send_datatype, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (protocol_probe::active) { + ++protocol_probe::neighbor_count_calls; + } + return PMPI_Neighbor_alltoall(send_buffer, send_count, send_datatype, + receive_buffer, receive_count, receive_datatype, + communicator); +} + +extern "C" int MPI_Neighbor_alltoallv(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (protocol_probe::active) { + ++protocol_probe::neighbor_payload_calls; + protocol_probe::record_extent(send_datatype, + protocol_probe::neighbor_payload_extents); + protocol_probe::record_graph_neighbors(communicator); + protocol_probe::record_neighbor_send_counts(send_counts, communicator); + } + auto const result = PMPI_Neighbor_alltoallv( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator); + if (protocol_probe::active && result == MPI_SUCCESS) { + protocol_probe::mutate_neighbor_payload(receive_buffer, receive_counts, + receive_displacements, + receive_datatype, communicator); + } + return result; +} + +#if KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C +extern "C" int MPI_Neighbor_alltoallv_c(void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (protocol_probe::active) { + ++protocol_probe::neighbor_payload_c_calls; + protocol_probe::record_extent(send_datatype, + protocol_probe::neighbor_payload_extents); + protocol_probe::record_graph_neighbors(communicator); + protocol_probe::record_neighbor_send_counts(send_counts, communicator); + } + auto const result = PMPI_Neighbor_alltoallv_c( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator); + if (protocol_probe::active && result == MPI_SUCCESS) { + protocol_probe::mutate_neighbor_payload(receive_buffer, receive_counts, + receive_displacements, + receive_datatype, communicator); + } + return result; +} +#endif + +extern "C" int MPI_Isend(void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator, + MPI_Request* request) { + if (protocol_probe::active) { + ++protocol_probe::point_to_point_calls; + protocol_probe::record_tag11(tag, communicator, + protocol_probe::tag11_isend_calls); + } + return PMPI_Isend(buffer, count, datatype, destination, tag, communicator, + request); +} + +extern "C" int MPI_Probe(int source, + int tag, + MPI_Comm communicator, + MPI_Status* status) { + if (protocol_probe::active) { + ++protocol_probe::point_to_point_calls; + protocol_probe::record_tag11(tag, communicator, + protocol_probe::tag11_probe_calls); + } + return PMPI_Probe(source, tag, communicator, status); +} + +extern "C" int MPI_Recv(void* buffer, + int count, + MPI_Datatype datatype, + int source, + int tag, + MPI_Comm communicator, + MPI_Status* status) { + if (protocol_probe::active) { + ++protocol_probe::point_to_point_calls; + protocol_probe::record_tag11(tag, communicator, + protocol_probe::tag11_recv_calls); + } + return PMPI_Recv(buffer, count, datatype, source, tag, communicator, status); +} + +#define KAHIP_BLOCK_DOWN_P2P_WRAPPER(name, signature, arguments) \ + extern "C" int name signature { \ + if (protocol_probe::active) { \ + ++protocol_probe::point_to_point_calls; \ + } \ + return P##name arguments; \ + } + +KAHIP_BLOCK_DOWN_P2P_WRAPPER( + MPI_Send, + (void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator), + (buffer, count, datatype, destination, tag, communicator)) +KAHIP_BLOCK_DOWN_P2P_WRAPPER( + MPI_Ssend, + (void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator), + (buffer, count, datatype, destination, tag, communicator)) +KAHIP_BLOCK_DOWN_P2P_WRAPPER( + MPI_Bsend, + (void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator), + (buffer, count, datatype, destination, tag, communicator)) +KAHIP_BLOCK_DOWN_P2P_WRAPPER( + MPI_Rsend, + (void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator), + (buffer, count, datatype, destination, tag, communicator)) +KAHIP_BLOCK_DOWN_P2P_WRAPPER( + MPI_Issend, + (void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator, + MPI_Request* request), + (buffer, count, datatype, destination, tag, communicator, request)) +KAHIP_BLOCK_DOWN_P2P_WRAPPER( + MPI_Ibsend, + (void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator, + MPI_Request* request), + (buffer, count, datatype, destination, tag, communicator, request)) +KAHIP_BLOCK_DOWN_P2P_WRAPPER( + MPI_Irsend, + (void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator, + MPI_Request* request), + (buffer, count, datatype, destination, tag, communicator, request)) +KAHIP_BLOCK_DOWN_P2P_WRAPPER( + MPI_Irecv, + (void* buffer, + int count, + MPI_Datatype datatype, + int source, + int tag, + MPI_Comm communicator, + MPI_Request* request), + (buffer, count, datatype, source, tag, communicator, request)) +KAHIP_BLOCK_DOWN_P2P_WRAPPER( + MPI_Iprobe, + (int source, int tag, MPI_Comm communicator, int* flag, MPI_Status* status), + (source, tag, communicator, flag, status)) +KAHIP_BLOCK_DOWN_P2P_WRAPPER(MPI_Sendrecv, + (void const* send_buffer, + int send_count, + MPI_Datatype send_datatype, + int destination, + int send_tag, + void* receive_buffer, + int receive_count, + MPI_Datatype receive_datatype, + int source, + int receive_tag, + MPI_Comm communicator, + MPI_Status* status), + (send_buffer, + send_count, + send_datatype, + destination, + send_tag, + receive_buffer, + receive_count, + receive_datatype, + source, + receive_tag, + communicator, + status)) +KAHIP_BLOCK_DOWN_P2P_WRAPPER(MPI_Sendrecv_replace, + (void* buffer, + int count, + MPI_Datatype datatype, + int destination, + int send_tag, + int source, + int receive_tag, + MPI_Comm communicator, + MPI_Status* status), + (buffer, + count, + datatype, + destination, + send_tag, + source, + receive_tag, + communicator, + status)) +KAHIP_BLOCK_DOWN_P2P_WRAPPER(MPI_Mprobe, + (int source, + int tag, + MPI_Comm communicator, + MPI_Message* message, + MPI_Status* status), + (source, tag, communicator, message, status)) +KAHIP_BLOCK_DOWN_P2P_WRAPPER(MPI_Improbe, + (int source, + int tag, + MPI_Comm communicator, + int* flag, + MPI_Message* message, + MPI_Status* status), + (source, tag, communicator, flag, message, status)) +KAHIP_BLOCK_DOWN_P2P_WRAPPER(MPI_Mrecv, + (void* buffer, + int count, + MPI_Datatype datatype, + MPI_Message* message, + MPI_Status* status), + (buffer, count, datatype, message, status)) +KAHIP_BLOCK_DOWN_P2P_WRAPPER(MPI_Imrecv, + (void* buffer, + int count, + MPI_Datatype datatype, + MPI_Message* message, + MPI_Request* request), + (buffer, count, datatype, message, request)) + +#undef KAHIP_BLOCK_DOWN_P2P_WRAPPER + +extern "C" int MPI_Ineighbor_alltoallv(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Request* request) { + if (protocol_probe::active) { + ++protocol_probe::immediate_neighbor_calls; + } + return PMPI_Ineighbor_alltoallv(send_buffer, send_counts, send_displacements, + send_datatype, receive_buffer, receive_counts, + receive_displacements, receive_datatype, + communicator, request); +} + +#if KAHIP_HAVE_MPI_INEIGHBOR_ALLTOALLV_C +extern "C" int MPI_Ineighbor_alltoallv_c(void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Request* request) { + if (protocol_probe::active) { + ++protocol_probe::immediate_neighbor_calls; + } + return PMPI_Ineighbor_alltoallv_c( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator, request); +} +#endif + +#if KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT +extern "C" int MPI_Neighbor_alltoallv_init(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Info info, + MPI_Request* request) { + if (protocol_probe::active) { + ++protocol_probe::persistent_calls; + } + return PMPI_Neighbor_alltoallv_init( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator, info, request); +} +#endif + +#if KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_INIT_C +extern "C" int MPI_Neighbor_alltoallv_init_c( + void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator, + MPI_Info info, + MPI_Request* request) { + if (protocol_probe::active) { + ++protocol_probe::persistent_calls; + } + return PMPI_Neighbor_alltoallv_init_c( + send_buffer, send_counts, send_displacements, send_datatype, + receive_buffer, receive_counts, receive_displacements, receive_datatype, + communicator, info, request); +} +#endif + +extern "C" int MPI_Start(MPI_Request* request) { + if (protocol_probe::active) { + ++protocol_probe::persistent_calls; + } + return PMPI_Start(request); +} + +extern "C" int MPI_Startall(int count, MPI_Request requests[]) { + if (protocol_probe::active) { + ++protocol_probe::persistent_calls; + } + return PMPI_Startall(count, requests); +} + +#define KAHIP_BLOCK_DOWN_COMPLETION_WRAPPER(name, signature, arguments) \ + extern "C" int name signature { \ + if (protocol_probe::active) { \ + ++protocol_probe::completion_calls; \ + } \ + return P##name arguments; \ + } + +KAHIP_BLOCK_DOWN_COMPLETION_WRAPPER(MPI_Test, + (MPI_Request * request, + int* complete, + MPI_Status* status), + (request, complete, status)) +KAHIP_BLOCK_DOWN_COMPLETION_WRAPPER(MPI_Wait, + (MPI_Request * request, MPI_Status* status), + (request, status)) +KAHIP_BLOCK_DOWN_COMPLETION_WRAPPER(MPI_Waitall, + (int count, + MPI_Request requests[], + MPI_Status statuses[]), + (count, requests, statuses)) +KAHIP_BLOCK_DOWN_COMPLETION_WRAPPER( + MPI_Testall, + (int count, MPI_Request requests[], int* complete, MPI_Status statuses[]), + (count, requests, complete, statuses)) +KAHIP_BLOCK_DOWN_COMPLETION_WRAPPER(MPI_Testany, + (int count, + MPI_Request requests[], + int* index, + int* complete, + MPI_Status* status), + (count, requests, index, complete, status)) +KAHIP_BLOCK_DOWN_COMPLETION_WRAPPER( + MPI_Testsome, + (int count, + MPI_Request requests[], + int* completed, + int indices[], + MPI_Status statuses[]), + (count, requests, completed, indices, statuses)) +KAHIP_BLOCK_DOWN_COMPLETION_WRAPPER( + MPI_Waitany, + (int count, MPI_Request requests[], int* index, MPI_Status* status), + (count, requests, index, status)) +KAHIP_BLOCK_DOWN_COMPLETION_WRAPPER( + MPI_Waitsome, + (int count, + MPI_Request requests[], + int* completed, + int indices[], + MPI_Status statuses[]), + (count, requests, completed, indices, statuses)) +KAHIP_BLOCK_DOWN_COMPLETION_WRAPPER(MPI_Request_free, + (MPI_Request * request), + (request)) +KAHIP_BLOCK_DOWN_COMPLETION_WRAPPER(MPI_Cancel, + (MPI_Request * request), + (request)) + +#undef KAHIP_BLOCK_DOWN_COMPLETION_WRAPPER + +extern "C" int MPI_Barrier(MPI_Comm communicator) { + if (protocol_probe::active) { + ++protocol_probe::barrier_calls; + } + return PMPI_Barrier(communicator); +} + +namespace { +void require_common(bool local_condition) { + auto const local = local_condition ? 1 : 0; + auto common = 0; + REQUIRE(PMPI_Allreduce(&local, &common, 1, MPI_INT, MPI_MIN, + MPI_COMM_WORLD) == MPI_SUCCESS); + REQUIRE(common == 1); +} + +void build_cross_rank_coarse(parhip::parallel_graph_access& graph, int rank) { + constexpr auto global_nodes = parhip::NodeID{2}; + constexpr auto global_edges = parhip::EdgeID{4}; + graph.start_construction(1, 2, global_nodes, global_edges, false); + graph.set_range(static_cast(rank), + static_cast(rank)); + auto ranges = std::vector{0, 1, 2}; + graph.set_range_array(ranges); + auto const local = graph.new_node(); + graph.setNodeWeight(local, 1); + graph.setNodeLabel(local, static_cast(rank)); + graph.setSecondPartitionIndex(local, 2); + for (auto duplicate = 0; duplicate < 2; ++duplicate) { + auto const edge = + graph.new_edge(local, static_cast(1 - rank)); + graph.setEdgeWeight(edge, 1); + } + graph.finish_construction(); + auto const storage_size = graph.number_of_local_nodes() + parhip::NodeID{1} + + graph.number_of_ghost_nodes(); + for (auto local_id = parhip::NodeID{0}; local_id < storage_size; ++local_id) { + graph.setSecondPartitionIndex( + local_id, + parhip::PartitionID{2} + static_cast(local_id)); + } +} + +void build_cross_rank_finer(parhip::parallel_graph_access& graph, int rank) { + graph.start_construction(2, 0, 4, 0, false); + auto const first = static_cast(2 * rank); + graph.set_range(first, first + parhip::NodeID{1}); + auto ranges = std::vector{0, 2, 4}; + graph.set_range_array(ranges); + for (auto coarse = parhip::NodeID{0}; coarse < parhip::NodeID{2}; ++coarse) { + auto const local = graph.new_node(); + graph.setNodeWeight(local, 1); + graph.setNodeLabel(local, first + local); + graph.setSecondPartitionIndex( + local, + parhip::PartitionID{10} + static_cast(coarse)); + } + graph.finish_construction(); + graph.allocate_node_to_cnode(); + graph.setCNode(0, 0); + graph.setCNode(1, 1); +} + +[[nodiscard]] auto expected_trace(int rank) -> std::string { + auto text = std::string{ + "kahip-mpi-trace-v3 upstream=" + "5935f349f65f1788a9b68fcf6d853e698d86956d\n"}; + for (auto global = 0; global < 2; ++global) { + text += + "block-propagation cycle=3 level=2 epoch=contraction iteration=0 " + "round=0 global=" + + std::to_string(global) + " owner=" + std::to_string(global) + + " requester=- receiver=" + std::to_string(rank) + + " key=block block=" + std::to_string(10 + global) + "\n"; + } + return text; +} + +struct distributed_fixture { + std::vector> adjacency; +}; + +[[nodiscard]] auto fixture_for_size(int size) -> distributed_fixture { + switch (size) { + case 1: + return {{{}}}; + case 2: + return {{{1, 1}, {0, 0}}}; + case 3: + return {{{2, 3}, {2, 3}, {0, 1}, {0, 1}}}; + case 4: + return {{{1, 3}, {0, 2}, {1, 3}, {0, 2}}}; + case 5: + return {{{1}, {0, 2}, {1, 3}, {2}, {}}}; + default: + throw std::invalid_argument{"block-down fixture requires ranks 1-5"}; + } +} + +[[nodiscard]] auto block_for(parhip::NodeID global_id, + parhip::PartitionID epoch = 0) + -> parhip::PartitionID { + constexpr auto domain = parhip::PartitionID{14}; + return (parhip::PartitionID{10} + + static_cast(global_id) + epoch) % + domain; +} + +[[nodiscard]] auto fixture_ranges(parhip::NodeID global_nodes, int size) + -> std::vector { + auto const ownership = parhip::mpi::contiguous_owner_layout{ + global_nodes, static_cast(size)}; + auto ranges = std::vector(static_cast(size) + + std::size_t{1}); + for (auto rank = std::size_t{0}; rank < ranges.size(); ++rank) { + ranges[rank] = ownership.boundary(rank); + } + return ranges; +} + +void build_coarse_fixture( + parhip::parallel_graph_access& graph, + distributed_fixture const& fixture, + int rank, + int size, + std::optional global_override = std::nullopt) { + auto const global_nodes = + static_cast(fixture.adjacency.size()); + auto const ranges = fixture_ranges(global_nodes, size); + auto const first = ranges[static_cast(rank)]; + auto const end = ranges[static_cast(rank + 1)]; + auto local_edges = std::size_t{0}; + for (auto global = first; global < end; ++global) { + local_edges += fixture.adjacency[static_cast(global)].size(); + } + auto global_edges = std::size_t{0}; + for (auto const& adjacency : fixture.adjacency) { + global_edges += adjacency.size(); + } + graph.start_construction(end - first, + static_cast(local_edges), + global_override.value_or(global_nodes), + static_cast(global_edges), false); + graph.set_range(first, first == end ? first : end - parhip::NodeID{1}); + auto mutable_ranges = ranges; + graph.set_range_array(mutable_ranges); + for (auto global = first; global < end; ++global) { + auto const local = graph.new_node(); + graph.setNodeWeight(local, 1); + graph.setNodeLabel(local, global); + graph.setSecondPartitionIndex(local, parhip::PartitionID{2} + local); + for (auto const target : + fixture.adjacency[static_cast(global)]) { + auto const edge = graph.new_edge(local, target); + graph.setEdgeWeight(edge, 1); + } + } + graph.finish_construction(); + auto const storage_size = graph.number_of_local_nodes() + parhip::NodeID{1} + + graph.number_of_ghost_nodes(); + for (auto local = parhip::NodeID{0}; local < storage_size; ++local) { + graph.setSecondPartitionIndex( + local, + parhip::PartitionID{2} + static_cast(local)); + } +} + +void build_finer_fixture(parhip::parallel_graph_access& graph, + parhip::NodeID coarse_nodes, + int rank, + int size, + parhip::PartitionID epoch = 0) { + auto const ranges = fixture_ranges(coarse_nodes, size); + auto const first = ranges[static_cast(rank)]; + auto const end = ranges[static_cast(rank + 1)]; + graph.start_construction(end - first, 0, coarse_nodes, 0, false); + graph.set_range(first, first == end ? first : end - parhip::NodeID{1}); + auto mutable_ranges = ranges; + graph.set_range_array(mutable_ranges); + for (auto global = first; global < end; ++global) { + auto const coarse = coarse_nodes == 0 + ? parhip::NodeID{0} + : (global + parhip::NodeID{1}) % coarse_nodes; + auto const local = graph.new_node(); + graph.setNodeWeight(local, 1); + graph.setNodeLabel(local, global); + graph.setSecondPartitionIndex(local, block_for(coarse, epoch)); + } + graph.finish_construction(); + graph.allocate_node_to_cnode(); + for (auto global = first; global < end; ++global) { + auto const coarse = coarse_nodes == 0 + ? parhip::NodeID{0} + : (global + parhip::NodeID{1}) % coarse_nodes; + graph.setCNode(global - first, coarse); + } +} + +[[nodiscard]] auto snapshot_blocks(parhip::parallel_graph_access& graph) + -> std::vector { + auto values = std::vector{}; + auto const storage_size = graph.number_of_local_nodes() + parhip::NodeID{1} + + graph.number_of_ghost_nodes(); + values.reserve(static_cast(storage_size)); + for (auto local = parhip::NodeID{0}; local < storage_size; ++local) { + values.push_back( + static_cast(graph.getSecondPartitionIndex(local))); + } + return values; +} + +[[nodiscard]] auto blocks_match(parhip::parallel_graph_access& graph, + parhip::PartitionID epoch = 0) -> bool { + auto valid = true; + for (auto local = parhip::NodeID{0}; local < graph.number_of_local_nodes(); + ++local) { + valid = valid && graph.getSecondPartitionIndex(local) == + block_for(graph.getGlobalID(local), epoch); + } + auto const sentinel = graph.number_of_local_nodes(); + valid = valid && graph.getSecondPartitionIndex(sentinel) == + parhip::PartitionID{2} + + static_cast(sentinel); + for (auto local = graph.number_of_local_nodes() + parhip::NodeID{1}; + local < graph.number_of_local_nodes() + parhip::NodeID{1} + + graph.number_of_ghost_nodes(); + ++local) { + valid = valid && graph.getSecondPartitionIndex(local) == + block_for(graph.getGlobalID(local), epoch); + } + return valid; +} + +[[nodiscard]] auto exact_protocol(parhip::parallel_graph_access& graph, + int topology_creations) -> bool { + auto const& plan = graph.ghost_plan(); +#if KAHIP_HAVE_MPI_ALLTOALLV_C + auto const dense_path_is_exact = protocol_probe::dense_payload_calls == 0 && + protocol_probe::dense_payload_c_calls == 1; +#else + auto const dense_path_is_exact = protocol_probe::dense_payload_calls == 1 && + protocol_probe::dense_payload_c_calls == 0; +#endif +#if KAHIP_HAVE_MPI_NEIGHBOR_ALLTOALLV_C + auto const neighbor_path_is_exact = + protocol_probe::neighbor_payload_calls == 0 && + protocol_probe::neighbor_payload_c_calls == 1; +#else + auto const neighbor_path_is_exact = + protocol_probe::neighbor_payload_calls == 1 && + protocol_probe::neighbor_payload_c_calls == 0; +#endif + auto valid = + !protocol_probe::interposer_error && + !protocol_probe::dense_payload_extents.overflowed() && + !protocol_probe::neighbor_payload_extents.overflowed() && + !protocol_probe::neighbor_sources.overflowed() && + !protocol_probe::neighbor_destinations.overflowed() && + !protocol_probe::neighbor_send_counts.overflowed() && + protocol_probe::dense_payload_extents.size() == 1 && + *protocol_probe::dense_payload_extents.begin() == + static_cast(sizeof(parhip::block_down::block_update)) && + protocol_probe::neighbor_payload_extents.size() == 1 && + *protocol_probe::neighbor_payload_extents.begin() == + static_cast(sizeof(parhip::block_down::block_update)) && + dense_path_is_exact && neighbor_path_is_exact && + protocol_probe::topology_create_calls == topology_creations && + protocol_probe::neighbor_count_calls == 1 && + protocol_probe::point_to_point_calls == 0 && + protocol_probe::immediate_neighbor_calls == 0 && + protocol_probe::persistent_calls == 0 && + protocol_probe::completion_calls == 0 && + protocol_probe::barrier_calls == 0 && + protocol_probe::tag11_isend_calls == 0 && + protocol_probe::tag11_probe_calls == 0 && + protocol_probe::tag11_recv_calls == 0 && + std::ranges::equal(protocol_probe::neighbor_sources, + plan.topology().sources()) && + std::ranges::equal(protocol_probe::neighbor_destinations, + plan.topology().destinations()) && + protocol_probe::neighbor_send_counts.size() == + plan.topology().destinations().size(); + if (protocol_probe::neighbor_send_counts.size() == + plan.topology().destinations().size()) { + for (auto index = std::size_t{0}; + index < plan.topology().destinations().size(); ++index) { + valid = valid && *std::next(protocol_probe::neighbor_send_counts.begin(), + static_cast(index)) == + plan.outgoing_local_nodes(index).size(); + } + } + return valid; +} + +template +void require_collective_failure(Operation&& operation, + std::string_view expected_context, + int size) { + auto caught = 0; + auto structured = 0; + auto context_matches = 0; + try { + std::forward(operation)(); + } catch (parhip::mpi::mpi_error const& error) { + caught = 1; + structured = error.error_code() == MPI_ERR_ARG ? 1 : 0; + context_matches = std::string_view{error.what()}.find(expected_context) != + std::string_view::npos + ? 1 + : 0; + } catch (...) { + caught = 1; + } + auto caught_total = 0; + auto structured_total = 0; + auto context_total = 0; + REQUIRE(PMPI_Allreduce(&caught, &caught_total, 1, MPI_INT, MPI_SUM, + MPI_COMM_WORLD) == MPI_SUCCESS); + REQUIRE(PMPI_Allreduce(&structured, &structured_total, 1, MPI_INT, MPI_SUM, + MPI_COMM_WORLD) == MPI_SUCCESS); + REQUIRE(PMPI_Allreduce(&context_matches, &context_total, 1, MPI_INT, MPI_SUM, + MPI_COMM_WORLD) == MPI_SUCCESS); + REQUIRE(caught_total == size); + REQUIRE(structured_total == size); + REQUIRE(context_total == size); +} + +class trace_session final { + public: + trace_session() { + parhip::mpi::trace::reset(); + parhip::mpi::trace::set_active(true); + } + ~trace_session() { + parhip::mpi::trace::set_active(false); + parhip::mpi::trace::reset(); + } + + trace_session(trace_session const&) = delete; + auto operator=(trace_session const&) -> trace_session& = delete; +}; +} // namespace + +TEST_CASE( + "block-down uses one typed dense and one blocking neighborhood transaction", + "[mpi][block-down][protocol][transaction]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 2) { + return; + } + + auto finer = parhip::parallel_graph_access{MPI_COMM_WORLD}; + auto coarser = parhip::parallel_graph_access{MPI_COMM_WORLD}; + build_cross_rank_finer(finer, rank); + build_cross_rank_coarse(coarser, rank); + auto config = parhip::PPartitionConfig{}; + config.k = 14; + + auto trace = trace_session{}; + KAHIP_MPI_TRACE_SET_HIERARCHY(3, 2, parhip::mpi::trace::epoch::contraction); + auto probe = protocol_probe::activation{}; + parhip::parallel_block_down_propagation{}.propagate_block_down( + MPI_COMM_WORLD, config, finer, coarser); + + auto const ghost = coarser.number_of_local_nodes() + parhip::NodeID{1}; +#if KAHIP_ENABLE_MPI_TRACE + auto const trace_is_exact = + parhip::mpi::trace::canonical_text(parhip::mpi::trace::snapshot()) == + expected_trace(rank); +#else + auto const trace_is_exact = parhip::mpi::trace::snapshot().empty(); +#endif + auto const state_and_trace_are_exact = + coarser.getSecondPartitionIndex(0) == + parhip::PartitionID{10} + static_cast(rank) && + coarser.getSecondPartitionIndex(ghost) == + parhip::PartitionID{10} + + static_cast(1 - rank) && + trace_is_exact; + require_common(state_and_trace_are_exact); + + auto const dense_calls = protocol_probe::dense_payload_calls + + protocol_probe::dense_payload_c_calls; + auto const neighbor_calls = protocol_probe::neighbor_payload_calls + + protocol_probe::neighbor_payload_c_calls; + CAPTURE(rank, dense_calls, protocol_probe::topology_create_calls, + protocol_probe::neighbor_count_calls, neighbor_calls, + protocol_probe::point_to_point_calls, + protocol_probe::tag11_isend_calls, protocol_probe::tag11_probe_calls, + protocol_probe::tag11_recv_calls); + require_common(exact_protocol(coarser, 1)); +} + +TEST_CASE("block-down wire datatype has exact semantic extent", + "[unit][mpi][block-down][datatype]") { + STATIC_REQUIRE(std::is_standard_layout_v); + STATIC_REQUIRE( + std::is_trivially_copyable_v); + auto datatype = + parhip::mpi::make_mpi_datatype(); + auto lower_bound = MPI_Aint{0}; + auto extent = MPI_Aint{0}; + REQUIRE(MPI_Type_get_extent(datatype.native_handle(), &lower_bound, + &extent) == MPI_SUCCESS); + REQUIRE(lower_bound == 0); + REQUIRE(extent == + static_cast(sizeof(parhip::block_down::block_update))); +} + +TEST_CASE("block-down covers distributed rank-one through rank-five shapes", + "[mpi][block-down][matrix][protocol]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE(size >= 1); + REQUIRE(size <= 5); + + auto const fixture = fixture_for_size(size); + auto finer = parhip::parallel_graph_access{MPI_COMM_WORLD}; + auto coarser = parhip::parallel_graph_access{MPI_COMM_WORLD}; + build_finer_fixture( + finer, static_cast(fixture.adjacency.size()), rank, size); + build_coarse_fixture(coarser, fixture, rank, size); + auto config = parhip::PPartitionConfig{}; + config.k = 14; + auto trace = trace_session{}; + KAHIP_MPI_TRACE_SET_HIERARCHY(4, 1, parhip::mpi::trace::epoch::contraction); + auto probe = protocol_probe::activation{}; + parhip::parallel_block_down_propagation{}.propagate_block_down( + MPI_COMM_WORLD, config, finer, coarser); + + auto local_is_exact = blocks_match(coarser) && exact_protocol(coarser, 1); +#if KAHIP_ENABLE_MPI_TRACE + local_is_exact = local_is_exact && parhip::mpi::trace::snapshot().size() == + static_cast( + coarser.number_of_local_nodes() + + coarser.number_of_ghost_nodes()); +#else + local_is_exact = local_is_exact && parhip::mpi::trace::snapshot().empty(); +#endif + require_common(local_is_exact); +} + +TEST_CASE("globally empty block-down participates without sentinels", + "[mpi][block-down][empty][protocol]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 1) { + return; + } + + auto const fixture = distributed_fixture{{}}; + auto finer = parhip::parallel_graph_access{MPI_COMM_WORLD}; + auto coarser = parhip::parallel_graph_access{MPI_COMM_WORLD}; + build_finer_fixture(finer, 0, rank, size); + build_coarse_fixture(coarser, fixture, rank, size); + auto config = parhip::PPartitionConfig{}; + config.k = 1; + auto trace = trace_session{}; + auto probe = protocol_probe::activation{}; + parhip::parallel_block_down_propagation{}.propagate_block_down( + MPI_COMM_WORLD, config, finer, coarser); + require_common(blocks_match(coarser) && exact_protocol(coarser, 1) && + parhip::mpi::trace::snapshot().empty()); +} + +TEST_CASE("block-down reuses a warm topology and refreshes staged blocks", + "[mpi][block-down][reuse][protocol]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 2) { + return; + } + + auto const fixture = fixture_for_size(size); + auto finer = parhip::parallel_graph_access{MPI_COMM_WORLD}; + auto coarser = parhip::parallel_graph_access{MPI_COMM_WORLD}; + build_finer_fixture(finer, 2, rank, size); + build_coarse_fixture(coarser, fixture, rank, size); + auto config = parhip::PPartitionConfig{}; + config.k = 14; + auto trace = trace_session{}; + auto probe = protocol_probe::activation{}; + static_cast(coarser.ghost_plan()); + require_common(protocol_probe::topology_create_calls == 1); + + protocol_probe::reset(); + parhip::parallel_block_down_propagation{}.propagate_block_down( + MPI_COMM_WORLD, config, finer, coarser); + require_common(blocks_match(coarser) && exact_protocol(coarser, 0)); + + for (auto local = parhip::NodeID{0}; local < finer.number_of_local_nodes(); + ++local) { + finer.setSecondPartitionIndex(local, block_for(finer.getCNode(local), 3)); + } + protocol_probe::reset(); + parhip::parallel_block_down_propagation{}.propagate_block_down( + MPI_COMM_WORLD, config, finer, coarser); + require_common(blocks_match(coarser, 3) && exact_protocol(coarser, 0)); +} + +TEST_CASE("dense block-down receive failures preserve state and retry", + "[mpi][block-down][dense][failure][transaction]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 2) { + return; + } + + constexpr auto modes = + std::array{protocol_probe::receive_mutation::dense_unknown_id, + protocol_probe::receive_mutation::dense_block_equal_k, + protocol_probe::receive_mutation::dense_conflicting_duplicate}; + for (auto const mode : modes) { + auto finer = parhip::parallel_graph_access{MPI_COMM_WORLD}; + auto coarser = parhip::parallel_graph_access{MPI_COMM_WORLD}; + build_cross_rank_finer(finer, rank); + build_cross_rank_coarse(coarser, rank); + auto config = parhip::PPartitionConfig{}; + config.k = 14; + auto const before_blocks = snapshot_blocks(coarser); + auto trace = trace_session{}; +#if KAHIP_ENABLE_MPI_TRACE + parhip::mpi::trace::append(parhip::mpi::trace::block_propagation( + parhip::mpi::trace::current_hierarchy(), 777, rank, rank, 3)); +#endif + auto const before_trace = parhip::mpi::trace::snapshot(); + auto probe = protocol_probe::activation{mode, 0, 14}; + require_collective_failure( + [&] { + parhip::parallel_block_down_propagation{}.propagate_block_down( + MPI_COMM_WORLD, config, finer, coarser); + }, + "block-down dense received validation failed", size); + auto const fired = protocol_probe::corruption_fired ? 1 : 0; + auto fired_total = 0; + REQUIRE(PMPI_Allreduce(&fired, &fired_total, 1, MPI_INT, MPI_SUM, + MPI_COMM_WORLD) == MPI_SUCCESS); + require_common(fired_total == 1 && !protocol_probe::interposer_error && + snapshot_blocks(coarser) == before_blocks && + parhip::mpi::trace::snapshot() == before_trace && + protocol_probe::topology_create_calls == 1 && + protocol_probe::dense_payload_calls + + protocol_probe::dense_payload_c_calls == + 1 && + protocol_probe::neighbor_count_calls == 0 && + protocol_probe::neighbor_payload_calls + + protocol_probe::neighbor_payload_c_calls == + 0 && + protocol_probe::point_to_point_calls == 0); + + protocol_probe::reset(); + parhip::parallel_block_down_propagation{}.propagate_block_down( + MPI_COMM_WORLD, config, finer, coarser); + require_common(blocks_match(coarser) && exact_protocol(coarser, 0)); + } +} + +TEST_CASE("dense block-down exact coverage rejects a replaced owner record", + "[mpi][block-down][dense][coverage][failure][transaction]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + auto const fixture = fixture_for_size(size); + auto finer = parhip::parallel_graph_access{MPI_COMM_WORLD}; + auto coarser = parhip::parallel_graph_access{MPI_COMM_WORLD}; + build_finer_fixture(finer, 4, rank, size); + build_coarse_fixture(coarser, fixture, rank, size); + auto config = parhip::PPartitionConfig{}; + config.k = 14; + auto const before_blocks = snapshot_blocks(coarser); + auto trace = trace_session{}; +#if KAHIP_ENABLE_MPI_TRACE + parhip::mpi::trace::append(parhip::mpi::trace::block_propagation( + parhip::mpi::trace::current_hierarchy(), 777, rank, rank, 3)); +#endif + auto const before_trace = parhip::mpi::trace::snapshot(); + auto probe = protocol_probe::activation{ + protocol_probe::receive_mutation::dense_duplicate_replacing_missing, 0, + 14}; + require_collective_failure( + [&] { + parhip::parallel_block_down_propagation{}.propagate_block_down( + MPI_COMM_WORLD, config, finer, coarser); + }, + "block-down dense received validation failed", size); + auto const fired = protocol_probe::corruption_fired ? 1 : 0; + auto fired_total = 0; + REQUIRE(PMPI_Allreduce(&fired, &fired_total, 1, MPI_INT, MPI_SUM, + MPI_COMM_WORLD) == MPI_SUCCESS); + auto const blocks_preserved = snapshot_blocks(coarser) == before_blocks; + auto const trace_preserved = parhip::mpi::trace::snapshot() == before_trace; + CAPTURE(rank, fired_total, protocol_probe::interposer_error, blocks_preserved, + trace_preserved, protocol_probe::topology_create_calls, + protocol_probe::dense_payload_calls, + protocol_probe::dense_payload_c_calls); + require_common(fired_total == 1 && !protocol_probe::interposer_error && + blocks_preserved && trace_preserved); + protocol_probe::reset(); + parhip::parallel_block_down_propagation{}.propagate_block_down( + MPI_COMM_WORLD, config, finer, coarser); + require_common(blocks_match(coarser) && exact_protocol(coarser, 0)); +} + +TEST_CASE("neighborhood block-down failures preserve state and retry", + "[mpi][block-down][neighbor][failure][transaction]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size < 2 || size > 4) { + return; + } + + auto modes = std::vector{}; + auto target_rank = 0; + auto replacement_id = parhip::NodeID{0}; + if (size == 2) { + modes = {protocol_probe::receive_mutation::neighbor_unknown_id, + protocol_probe::receive_mutation::neighbor_missing_extra, + protocol_probe::receive_mutation::neighbor_block_equal_k}; + } else if (size == 3) { + modes = { + protocol_probe::receive_mutation::neighbor_duplicate_replacing_missing}; + target_rank = 1; + replacement_id = 3; + } else { + modes = {protocol_probe::receive_mutation::neighbor_wrong_source}; + } + + for (auto const mode : modes) { + auto const fixture = fixture_for_size(size); + auto finer = parhip::parallel_graph_access{MPI_COMM_WORLD}; + auto coarser = parhip::parallel_graph_access{MPI_COMM_WORLD}; + build_finer_fixture(finer, + static_cast(fixture.adjacency.size()), + rank, size); + build_coarse_fixture(coarser, fixture, rank, size); + auto config = parhip::PPartitionConfig{}; + config.k = 14; + auto const before_blocks = snapshot_blocks(coarser); + auto trace = trace_session{}; +#if KAHIP_ENABLE_MPI_TRACE + parhip::mpi::trace::append(parhip::mpi::trace::block_propagation( + parhip::mpi::trace::current_hierarchy(), 777, rank, rank, 3)); +#endif + auto const before_trace = parhip::mpi::trace::snapshot(); + auto probe = + protocol_probe::activation{mode, target_rank, 14, replacement_id}; + require_collective_failure( + [&] { + parhip::parallel_block_down_propagation{}.propagate_block_down( + MPI_COMM_WORLD, config, finer, coarser); + }, + "block-down neighbor received validation failed", size); + auto const fired = protocol_probe::corruption_fired ? 1 : 0; + auto fired_total = 0; + REQUIRE(PMPI_Allreduce(&fired, &fired_total, 1, MPI_INT, MPI_SUM, + MPI_COMM_WORLD) == MPI_SUCCESS); + require_common(fired_total == 1 && !protocol_probe::interposer_error && + snapshot_blocks(coarser) == before_blocks && + parhip::mpi::trace::snapshot() == before_trace && + protocol_probe::topology_create_calls == 1 && + protocol_probe::dense_payload_calls + + protocol_probe::dense_payload_c_calls == + 1 && + protocol_probe::neighbor_count_calls == 1 && + protocol_probe::neighbor_payload_calls + + protocol_probe::neighbor_payload_c_calls == + 1 && + protocol_probe::point_to_point_calls == 0 && + protocol_probe::immediate_neighbor_calls == 0 && + protocol_probe::persistent_calls == 0 && + protocol_probe::completion_calls == 0 && + protocol_probe::barrier_calls == 0); + + protocol_probe::reset(); + parhip::parallel_block_down_propagation{}.propagate_block_down( + MPI_COMM_WORLD, config, finer, coarser); + require_common(blocks_match(coarser) && exact_protocol(coarser, 0)); + } +} + +TEST_CASE("block-down rejects a rank-local block equal to k before topology", + "[mpi][block-down][domain][failure][transaction]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 2) { + return; + } + auto finer = parhip::parallel_graph_access{MPI_COMM_WORLD}; + auto coarser = parhip::parallel_graph_access{MPI_COMM_WORLD}; + build_cross_rank_finer(finer, rank); + build_cross_rank_coarse(coarser, rank); + if (rank == 0) { + finer.setSecondPartitionIndex(0, 14); + } + auto config = parhip::PPartitionConfig{}; + config.k = 14; + auto const before_blocks = snapshot_blocks(coarser); + auto trace = trace_session{}; + auto const before_trace = parhip::mpi::trace::snapshot(); + auto probe = protocol_probe::activation{}; + require_collective_failure( + [&] { + parhip::parallel_block_down_propagation{}.propagate_block_down( + MPI_COMM_WORLD, config, finer, coarser); + }, + "block-down local update validation failed", size); + require_common(snapshot_blocks(coarser) == before_blocks && + parhip::mpi::trace::snapshot() == before_trace && + protocol_probe::topology_create_calls == 0 && + protocol_probe::dense_payload_calls + + protocol_probe::dense_payload_c_calls == + 0 && + protocol_probe::neighbor_count_calls == 0 && + protocol_probe::point_to_point_calls == 0); +} + +TEST_CASE("block-down rejects rank-skewed k before topology", + "[mpi][block-down][k][failure][transaction]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 2) { + return; + } + auto finer = parhip::parallel_graph_access{MPI_COMM_WORLD}; + auto coarser = parhip::parallel_graph_access{MPI_COMM_WORLD}; + build_cross_rank_finer(finer, rank); + build_cross_rank_coarse(coarser, rank); + auto config = parhip::PPartitionConfig{}; + config.k = rank == 0 ? parhip::PartitionID{14} : parhip::PartitionID{13}; + auto const before_blocks = snapshot_blocks(coarser); + auto trace = trace_session{}; + auto const before_trace = parhip::mpi::trace::snapshot(); + auto probe = protocol_probe::activation{}; + require_collective_failure( + [&] { + parhip::parallel_block_down_propagation{}.propagate_block_down( + MPI_COMM_WORLD, config, finer, coarser); + }, + "block-down block-count agreement failed", size); + require_common(snapshot_blocks(coarser) == before_blocks && + parhip::mpi::trace::snapshot() == before_trace && + protocol_probe::topology_create_calls == 0 && + protocol_probe::dense_payload_calls + + protocol_probe::dense_payload_c_calls == + 0); +} + +TEST_CASE("block-down rejects zero k before topology", + "[mpi][block-down][k][failure][transaction]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 2) { + return; + } + auto finer = parhip::parallel_graph_access{MPI_COMM_WORLD}; + auto coarser = parhip::parallel_graph_access{MPI_COMM_WORLD}; + build_cross_rank_finer(finer, rank); + build_cross_rank_coarse(coarser, rank); + auto config = parhip::PPartitionConfig{}; + config.k = 0; + auto const before_blocks = snapshot_blocks(coarser); + auto trace = trace_session{}; + auto const before_trace = parhip::mpi::trace::snapshot(); + auto probe = protocol_probe::activation{}; + require_collective_failure( + [&] { + parhip::parallel_block_down_propagation{}.propagate_block_down( + MPI_COMM_WORLD, config, finer, coarser); + }, + "block-down requires a positive block count", size); + require_common(snapshot_blocks(coarser) == before_blocks && + parhip::mpi::trace::snapshot() == before_trace && + protocol_probe::topology_create_calls == 0 && + protocol_probe::dense_payload_calls + + protocol_probe::dense_payload_c_calls == + 0); +} + +TEST_CASE("block-down accepts congruent and rejects similar communicators", + "[mpi][block-down][communicator]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 2) { + return; + } + + SECTION("congruent") { + auto finer = parhip::parallel_graph_access{MPI_COMM_WORLD}; + auto coarser = parhip::parallel_graph_access{MPI_COMM_WORLD}; + build_cross_rank_finer(finer, rank); + build_cross_rank_coarse(coarser, rank); + auto duplicate = MPI_COMM_NULL; + REQUIRE(MPI_Comm_dup(MPI_COMM_WORLD, &duplicate) == MPI_SUCCESS); + auto config = parhip::PPartitionConfig{}; + config.k = 14; + { + auto probe = protocol_probe::activation{}; + parhip::parallel_block_down_propagation{}.propagate_block_down( + duplicate, config, finer, coarser); + require_common(blocks_match(coarser) && exact_protocol(coarser, 1)); + } + REQUIRE(MPI_Comm_free(&duplicate) == MPI_SUCCESS); + } + + SECTION("similar") { + auto finer = parhip::parallel_graph_access{MPI_COMM_WORLD}; + auto coarser = parhip::parallel_graph_access{MPI_COMM_WORLD}; + build_cross_rank_finer(finer, rank); + build_cross_rank_coarse(coarser, rank); + auto similar = MPI_COMM_NULL; + REQUIRE(MPI_Comm_split(MPI_COMM_WORLD, 0, size - rank, &similar) == + MPI_SUCCESS); + auto config = parhip::PPartitionConfig{}; + config.k = 14; + auto const before_blocks = snapshot_blocks(coarser); + { + auto probe = protocol_probe::activation{}; + require_collective_failure( + [&] { + parhip::parallel_block_down_propagation{}.propagate_block_down( + similar, config, finer, coarser); + }, + "block-down communicator validation failed", size); + require_common(snapshot_blocks(coarser) == before_blocks && + protocol_probe::topology_create_calls == 0 && + protocol_probe::dense_payload_calls + + protocol_probe::dense_payload_c_calls == + 0); + } + REQUIRE(MPI_Comm_free(&similar) == MPI_SUCCESS); + } +} + +TEST_CASE("block-down rejects skewed coarse domains before topology", + "[mpi][block-down][coarse-domain][failure][transaction]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + auto const fixture = fixture_for_size(size); + auto finer = parhip::parallel_graph_access{MPI_COMM_WORLD}; + auto coarser = parhip::parallel_graph_access{MPI_COMM_WORLD}; + build_finer_fixture(finer, 4, rank, size); + build_coarse_fixture(coarser, fixture, rank, size, + rank == 0 ? std::optional{5} + : std::optional{4}); + auto config = parhip::PPartitionConfig{}; + config.k = 14; + auto const before_blocks = snapshot_blocks(coarser); + auto trace = trace_session{}; + auto const before_trace = parhip::mpi::trace::snapshot(); + auto probe = protocol_probe::activation{}; + require_collective_failure( + [&] { + parhip::parallel_block_down_propagation{}.propagate_block_down( + MPI_COMM_WORLD, config, finer, coarser); + }, + "block-down coarse-node count agreement failed", size); + require_common(snapshot_blocks(coarser) == before_blocks && + parhip::mpi::trace::snapshot() == before_trace && + protocol_probe::topology_create_calls == 0 && + protocol_probe::dense_payload_calls + + protocol_probe::dense_payload_c_calls == + 0); +} + +TEST_CASE("block-down rejects skewed ownership metadata before topology", + "[mpi][block-down][ownership][failure][transaction]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 2) { + return; + } + auto finer = parhip::parallel_graph_access{MPI_COMM_WORLD}; + auto coarser = parhip::parallel_graph_access{MPI_COMM_WORLD}; + build_cross_rank_finer(finer, rank); + build_cross_rank_coarse(coarser, rank); + if (rank == 0) { + coarser.get_range_array()[1] = coarser.number_of_global_nodes(); + } + auto config = parhip::PPartitionConfig{}; + config.k = 14; + auto const before_blocks = snapshot_blocks(coarser); + auto trace = trace_session{}; + auto const before_trace = parhip::mpi::trace::snapshot(); + auto probe = protocol_probe::activation{}; + require_collective_failure( + [&] { + parhip::parallel_block_down_propagation{}.propagate_block_down( + MPI_COMM_WORLD, config, finer, coarser); + }, + "block-down quotient ownership metadata validation failed", size); + require_common(snapshot_blocks(coarser) == before_blocks && + parhip::mpi::trace::snapshot() == before_trace && + protocol_probe::topology_create_calls == 0 && + protocol_probe::dense_payload_calls + + protocol_probe::dense_payload_c_calls == + 0); +} + +TEST_CASE("block-down rejects asymmetric ghost topology before dense payload", + "[mpi][block-down][asymmetric][failure][transaction]") { + auto rank = 0; + auto size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 2) { + return; + } + auto const fixture = distributed_fixture{{{1}, {}}}; + auto finer = parhip::parallel_graph_access{MPI_COMM_WORLD}; + auto coarser = parhip::parallel_graph_access{MPI_COMM_WORLD}; + build_finer_fixture(finer, 2, rank, size); + build_coarse_fixture(coarser, fixture, rank, size); + auto config = parhip::PPartitionConfig{}; + config.k = 14; + auto const before_blocks = snapshot_blocks(coarser); + auto trace = trace_session{}; + auto const before_trace = parhip::mpi::trace::snapshot(); + auto probe = protocol_probe::activation{}; + require_collective_failure( + [&] { + parhip::parallel_block_down_propagation{}.propagate_block_down( + MPI_COMM_WORLD, config, finer, coarser); + }, + "ghost exchange plan semantic validation failed", size); + require_common(snapshot_blocks(coarser) == before_blocks && + parhip::mpi::trace::snapshot() == before_trace && + protocol_probe::topology_create_calls == 1 && + protocol_probe::dense_payload_calls + + protocol_probe::dense_payload_c_calls == + 0 && + protocol_probe::neighbor_count_calls == 0 && + protocol_probe::point_to_point_calls == 0); +} diff --git a/parallel/parallel_src/tests/parallel_projection/parallel_projection_mpi_test.cpp b/parallel/parallel_src/tests/parallel_projection/parallel_projection_mpi_test.cpp new file mode 100644 index 00000000..6db5cd2b --- /dev/null +++ b/parallel/parallel_src/tests/parallel_projection/parallel_projection_mpi_test.cpp @@ -0,0 +1,1465 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "communication/contiguous_owner_layout.h" +#include "communication/mpi_error.h" +#include "communication/mpi_tools.h" +#include "communication/mpi_trace.h" +#include "data_structure/parallel_graph_access.h" +#include "kahip_mpi_capabilities.h" +#include "parallel_contraction_projection/parallel_block_down_propagation.h" +#include "parallel_contraction_projection/parallel_projection.h" +#include "parallel_label_compress/parallel_label_compress.h" + +// KAHIP_PMPI_CALLBACK_REGION_BEGIN +namespace protocol_probe { +inline bool active = false; +inline int all_to_all_v_calls = 0; +inline int all_to_all_v_c_calls = 0; +inline int isend_calls = 0; +inline int probe_calls = 0; +inline int recv_calls = 0; +inline bool interposer_error = false; +inline bool mutation_fired = false; + +enum class receive_mutation { + none, + projection_request_wrong_owner, + projection_reply_wrong_coarse_id, + projection_reply_duplicate_request, +}; + +inline receive_mutation mutation = receive_mutation::none; +inline int mutation_payload_ordinal = 0; +inline int mutation_target_rank = 0; + +void reset() noexcept { + active = false; + all_to_all_v_calls = 0; + all_to_all_v_c_calls = 0; + isend_calls = 0; + probe_calls = 0; + recv_calls = 0; + interposer_error = false; + mutation_fired = false; + mutation = receive_mutation::none; + mutation_payload_ordinal = 0; + mutation_target_rank = 0; +} + +[[nodiscard]] auto dense_payload_collective_calls() noexcept -> int { + return all_to_all_v_calls + all_to_all_v_c_calls; +} + +class activation final { + public: + explicit activation(receive_mutation selected = receive_mutation::none, + int payload_ordinal = 0, + int target_rank = 0) noexcept { + reset(); + mutation = selected; + mutation_payload_ordinal = payload_ordinal; + mutation_target_rank = target_rank; + active = true; + } + + ~activation() noexcept { active = false; } + + activation(activation const&) = delete; + auto operator=(activation const&) -> activation& = delete; +}; + +template +void mutate_received_payload(int payload_ordinal, + void* receive_buffer, + Count const receive_counts[], + Displacement const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) noexcept { + if (mutation == receive_mutation::none || + payload_ordinal != mutation_payload_ordinal) { + return; + } + + int rank = 0; + int size = 0; + if (PMPI_Comm_rank(communicator, &rank) != MPI_SUCCESS || + PMPI_Comm_size(communicator, &size) != MPI_SUCCESS || size < 0) { + interposer_error = true; + return; + } + if (rank != mutation_target_rank) { + return; + } + if (size != 0 && + (receive_counts == nullptr || receive_displacements == nullptr)) { + interposer_error = true; + return; + } + + MPI_Aint lower_bound = 0; + MPI_Aint extent = 0; + auto const expected_extent = + mutation == receive_mutation::projection_request_wrong_owner + ? static_cast(sizeof(parhip::projection::request)) + : static_cast(sizeof(parhip::projection::reply)); + if (PMPI_Type_get_extent(receive_datatype, &lower_bound, &extent) != + MPI_SUCCESS || + lower_bound != 0 || extent != expected_extent || + !std::in_range(extent)) { + interposer_error = true; + return; + } + for (int source = 0; source < size; ++source) { + if (receive_counts[source] <= 0) { + continue; + } + if (receive_buffer == nullptr || + !std::in_range(receive_displacements[source])) { + interposer_error = true; + return; + } + auto const displacement = + static_cast(receive_displacements[source]); + if (displacement < 0 || + displacement > std::numeric_limits::max() / extent) { + interposer_error = true; + return; + } + auto const byte_offset = displacement * extent; + if (!std::in_range(byte_offset)) { + interposer_error = true; + return; + } + auto* first_record = static_cast(receive_buffer) + + static_cast(byte_offset); + switch (mutation) { + case receive_mutation::projection_request_wrong_owner: + // The target rank owns coarse IDs 0 and 1 in this fixture. ID 2 is + // in-domain but owned by the sender, so the grouped receiver + // validation rejects the corrupted request. + reinterpret_cast(first_record) + ->coarse_global_id = parhip::NodeID{2}; + mutation_fired = true; + return; + case receive_mutation::projection_reply_wrong_coarse_id: + // Rank 0 requested coarse ID 2 from source 1. ID 3 has the same + // source owner but is not the coarse ID associated with the request. + reinterpret_cast(first_record) + ->coarse_global_id = parhip::NodeID{3}; + mutation_fired = true; + return; + case receive_mutation::projection_reply_duplicate_request: { + if (receive_counts[source] < 2) { + continue; + } + auto const record_extent = static_cast(extent); + if (static_cast(byte_offset) > + std::numeric_limits::max() - record_extent) { + interposer_error = true; + return; + } + auto* first = + reinterpret_cast(first_record); + auto* second = reinterpret_cast( + first_record + record_extent); + second->request_id = first->request_id; + second->coarse_global_id = first->coarse_global_id; + mutation_fired = true; + return; + } + case receive_mutation::none: + interposer_error = true; + return; + } + } + interposer_error = true; +} + +} // namespace protocol_probe + +static_assert(noexcept(protocol_probe::dense_payload_collective_calls())); +static_assert(noexcept( + protocol_probe::mutate_received_payload(0, + nullptr, + nullptr, + nullptr, + MPI_DATATYPE_NULL, + MPI_COMM_NULL))); +static_assert( + noexcept(protocol_probe::mutate_received_payload( + 0, + nullptr, + nullptr, + nullptr, + MPI_DATATYPE_NULL, + MPI_COMM_NULL))); + +extern "C" int MPI_Alltoallv(void const* send_buffer, + int const send_counts[], + int const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + int const receive_counts[], + int const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (protocol_probe::active) { + ++protocol_probe::all_to_all_v_calls; + } + auto const payload_ordinal = protocol_probe::dense_payload_collective_calls(); + auto const result = + PMPI_Alltoallv(send_buffer, send_counts, send_displacements, + send_datatype, receive_buffer, receive_counts, + receive_displacements, receive_datatype, communicator); + if (protocol_probe::active && result == MPI_SUCCESS) { + protocol_probe::mutate_received_payload( + payload_ordinal, receive_buffer, receive_counts, receive_displacements, + receive_datatype, communicator); + } + return result; +} + +#if KAHIP_HAVE_MPI_ALLTOALLV_C +extern "C" int MPI_Alltoallv_c(void const* send_buffer, + MPI_Count const send_counts[], + MPI_Aint const send_displacements[], + MPI_Datatype send_datatype, + void* receive_buffer, + MPI_Count const receive_counts[], + MPI_Aint const receive_displacements[], + MPI_Datatype receive_datatype, + MPI_Comm communicator) { + if (protocol_probe::active) { + ++protocol_probe::all_to_all_v_c_calls; + } + auto const payload_ordinal = protocol_probe::dense_payload_collective_calls(); + auto const result = + PMPI_Alltoallv_c(send_buffer, send_counts, send_displacements, + send_datatype, receive_buffer, receive_counts, + receive_displacements, receive_datatype, communicator); + if (protocol_probe::active && result == MPI_SUCCESS) { + protocol_probe::mutate_received_payload( + payload_ordinal, receive_buffer, receive_counts, receive_displacements, + receive_datatype, communicator); + } + return result; +} +#endif + +extern "C" int MPI_Isend(void const* buffer, + int count, + MPI_Datatype datatype, + int destination, + int tag, + MPI_Comm communicator, + MPI_Request* request) { + if (protocol_probe::active) { + ++protocol_probe::isend_calls; + } + return PMPI_Isend(buffer, count, datatype, destination, tag, communicator, + request); +} + +extern "C" int MPI_Probe(int source, + int tag, + MPI_Comm communicator, + MPI_Status* status) { + if (protocol_probe::active) { + ++protocol_probe::probe_calls; + } + return PMPI_Probe(source, tag, communicator, status); +} + +extern "C" int MPI_Recv(void* buffer, + int count, + MPI_Datatype datatype, + int source, + int tag, + MPI_Comm communicator, + MPI_Status* status) { + if (protocol_probe::active) { + ++protocol_probe::recv_calls; + } + return PMPI_Recv(buffer, count, datatype, source, tag, communicator, status); +} +// KAHIP_PMPI_CALLBACK_REGION_END + +namespace { +void require_callback_observation_is_safe(int expected_mutations) { + auto const local_error = + protocol_probe::active || protocol_probe::interposer_error ? 1 : 0; + auto error_count = 0; + REQUIRE(PMPI_Allreduce(&local_error, &error_count, 1, MPI_INT, MPI_SUM, + MPI_COMM_WORLD) == MPI_SUCCESS); + REQUIRE(error_count == 0); + + auto const local_mutation = protocol_probe::mutation_fired ? 1 : 0; + auto mutation_count = 0; + REQUIRE(PMPI_Allreduce(&local_mutation, &mutation_count, 1, MPI_INT, MPI_SUM, + MPI_COMM_WORLD) == MPI_SUCCESS); + REQUIRE(mutation_count == expected_mutations); +} + +void build_edgeless_graph(parhip::parallel_graph_access& graph, + int rank, + std::array labels) { + constexpr parhip::NodeID global_nodes = 4; + auto const first = static_cast(rank) * 2; + graph.start_construction(2, 0, global_nodes, 0, false); + graph.set_range(first, first + 1); + auto ranges = std::vector{0, 2, 4}; + graph.set_range_array(ranges); + for (parhip::NodeID index = 0; index < 2; ++index) { + auto const node = graph.new_node(); + graph.setNodeWeight(node, 1); + graph.setNodeLabel(node, labels[static_cast(index)]); + graph.setSecondPartitionIndex(node, 0); + } + graph.finish_construction(); +} + +void build_two_rank_cross_edge(parhip::parallel_graph_access& graph, int rank) { + constexpr parhip::NodeID global_nodes = 2; + constexpr parhip::EdgeID global_edges = 2; + graph.start_construction(1, 1, global_nodes, global_edges); + graph.set_range(static_cast(rank), + static_cast(rank)); + auto ranges = std::vector{0, 1, 2}; + graph.set_range_array(ranges); + + auto const node = graph.new_node(); + graph.setNodeWeight(node, 1); + graph.setNodeLabel(node, static_cast(rank)); + graph.setSecondPartitionIndex(node, 0); + auto const edge = graph.new_edge(node, static_cast(1 - rank)); + graph.setEdgeWeight(edge, 1); + graph.finish_construction(); +} + +void build_block_finer(parhip::parallel_graph_access& graph, + int rank, + int size) { + auto const global_nodes = static_cast(4 * size); + auto const first = static_cast(4 * rank); + graph.start_construction(4, 0, global_nodes, 0, false); + graph.set_range(first, first + 3); + auto ranges = std::vector(static_cast(size) + 1); + for (int pe = 0; pe <= size; ++pe) { + ranges[static_cast(pe)] = static_cast(4 * pe); + } + graph.set_range_array(ranges); + for (parhip::NodeID index = 0; index < 4; ++index) { + auto const node = graph.new_node(); + graph.setNodeWeight(node, 1); + graph.setNodeLabel(node, 0); + graph.setSecondPartitionIndex(node, 0); + } + graph.finish_construction(); +} + +void build_block_coarser(parhip::parallel_graph_access& graph, + int rank, + int size) { + constexpr auto global_nodes = parhip::NodeID{4}; + auto const ownership = parhip::mpi::contiguous_owner_layout{ + global_nodes, static_cast(size)}; + auto ranges = std::vector(static_cast(size) + 1); + for (int pe = 0; pe <= size; ++pe) { + ranges[static_cast(pe)] = + ownership.boundary(static_cast(pe)); + } + auto const first = ranges[static_cast(rank)]; + auto const end = ranges[static_cast(rank + 1)]; + auto const local_nodes = end - first; + graph.start_construction(local_nodes, 0, global_nodes, 0, false); + graph.set_range(first, local_nodes == 0 ? first : end - 1); + graph.set_range_array(ranges); + for (parhip::NodeID index = 0; index < local_nodes; ++index) { + auto const node = graph.new_node(); + graph.setNodeWeight(node, 1); + graph.setNodeLabel(node, 0); + graph.setSecondPartitionIndex(node, 0); + } + graph.finish_construction(); +} + +void build_empty_graph_with_global_count(parhip::parallel_graph_access& graph, + parhip::NodeID global_nodes, + int size) { + graph.start_construction(0, 0, global_nodes, 0, false); + graph.set_range(0, 0); + auto ranges = std::vector(static_cast(size) + 1, + parhip::NodeID{0}); + graph.set_range_array(ranges); + graph.finish_construction(); +} + +void build_projection_coarser(parhip::parallel_graph_access& graph, + int rank, + int size, + parhip::NodeID global_nodes) { + auto const ownership = parhip::mpi::contiguous_owner_layout{ + global_nodes, static_cast(size)}; + auto ranges = std::vector(static_cast(size) + 1); + for (int pe = 0; pe <= size; ++pe) { + ranges[static_cast(pe)] = + ownership.boundary(static_cast(pe)); + } + auto const first = ranges[static_cast(rank)]; + auto const end = ranges[static_cast(rank + 1)]; + auto const local_nodes = end - first; + graph.start_construction(local_nodes, 0, global_nodes, 0, false); + graph.set_range(first, local_nodes == 0 ? first : end - 1); + graph.set_range_array(ranges); + for (auto global = first; global < end; ++global) { + auto const node = graph.new_node(); + graph.setNodeWeight(node, 1); + graph.setNodeLabel(node, parhip::NodeID{100} + global); + graph.setSecondPartitionIndex(node, 0); + } + graph.finish_construction(); +} + +template +void build_projection_finer( + parhip::parallel_graph_access& graph, + int rank, + int size, + std::array const& coarse_nodes) { + auto const local_nodes = static_cast(Count); + auto const global_nodes = local_nodes * static_cast(size); + auto const first = local_nodes * static_cast(rank); + graph.start_construction(local_nodes, 0, global_nodes, 0, false); + graph.set_range(first, local_nodes == 0 ? first : first + local_nodes - 1); + auto ranges = std::vector(static_cast(size) + 1); + for (int pe = 0; pe <= size; ++pe) { + ranges[static_cast(pe)] = + local_nodes * static_cast(pe); + } + graph.set_range_array(ranges); + for (std::size_t index = 0; index < Count; ++index) { + auto const node = graph.new_node(); + graph.setNodeWeight(node, 1); + graph.setNodeLabel( + node, parhip::NodeID{900} + first + static_cast(index)); + graph.setSecondPartitionIndex(node, 0); + } + graph.finish_construction(); + graph.allocate_node_to_cnode(); + for (std::size_t index = 0; index < Count; ++index) { + graph.setCNode(static_cast(index), coarse_nodes[index]); + } +} + +template +void require_projection_labels_unchanged(parhip::parallel_graph_access& finer, + int rank) { + auto const first = + static_cast(Count) * static_cast(rank); + for (std::size_t index = 0; index < Count; ++index) { + REQUIRE(finer.getNodeLabel(static_cast(index)) == + parhip::NodeID{900} + first + static_cast(index)); + } +} + +template +void require_collective_validation_failure(Operation&& operation, + std::string_view expected_context, + int size) { + auto caught = 0; + auto structured = 0; + auto context_matches = 0; + try { + std::invoke(std::forward(operation)); + } catch (parhip::mpi::mpi_error const& error) { + caught = 1; + structured = 1; + context_matches = + error.context().find(expected_context) != std::string_view::npos ? 1 + : 0; + } catch (std::exception const&) { + caught = 1; + } + + auto caught_by_all = 0; + auto structured_by_all = 0; + auto context_matches_all = 0; + REQUIRE(MPI_Allreduce(&caught, &caught_by_all, 1, MPI_INT, MPI_SUM, + MPI_COMM_WORLD) == MPI_SUCCESS); + REQUIRE(MPI_Allreduce(&structured, &structured_by_all, 1, MPI_INT, MPI_SUM, + MPI_COMM_WORLD) == MPI_SUCCESS); + REQUIRE(MPI_Allreduce(&context_matches, &context_matches_all, 1, MPI_INT, + MPI_SUM, MPI_COMM_WORLD) == MPI_SUCCESS); + REQUIRE(caught_by_all == size); + REQUIRE(structured_by_all == size); + REQUIRE(context_matches_all == size); +} +} // namespace + +TEST_CASE("outer label iterations distinguish ghost exchanges after reset", + "[mpi][trace][ghost-update]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE(size == 2); + + parhip::parallel_graph_access graph{MPI_COMM_WORLD}; + build_two_rank_cross_edge(graph, rank); + parhip::PPartitionConfig config{}; + config.k = 1; + config.total_num_labels = 2; + config.label_iterations = 4; + config.upper_bound_cluster = 2; + config.node_ordering = parhip::NodeOrderingType::DEGREE_NODEORDERING; + config.vcycle = false; + graph.init_balance_management(config); + + parhip::mpi::trace::reset(); + parhip::mpi::trace::set_active(true); + KAHIP_MPI_TRACE_SET_HIERARCHY(5, 2, parhip::mpi::trace::epoch::coarsening); + parhip::parallel_label_compress< + std::unordered_map>{} + .perform_parallel_label_compression(config, graph, false); + +#if KAHIP_ENABLE_MPI_TRACE + auto const owner = 1 - rank; + auto const repeated_label = static_cast(owner); + auto const common_prefix = + std::string{"ghost-update cycle=5 level=2 epoch=coarsening iteration="}; + auto const common_suffix = + " round=1 global=" + std::to_string(owner) + + " owner=" + std::to_string(owner) + + " requester=- receiver=" + std::to_string(rank) + + " key=label label=" + std::to_string(repeated_label) + "\n"; + auto const trace = + parhip::mpi::trace::canonical_text(parhip::mpi::trace::snapshot()); + INFO(trace); + REQUIRE(trace.find(common_prefix + "1" + common_suffix) != std::string::npos); + REQUIRE(trace.find(common_prefix + "3" + common_suffix) != std::string::npos); +#else + REQUIRE(parhip::mpi::trace::snapshot().empty()); +#endif +} + +TEST_CASE("trace run ID mismatch fails collectively", "[mpi][trace][run-id]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE(size == 2); + +#if KAHIP_ENABLE_MPI_TRACE + auto failed = false; + try { + static_cast(parhip::mpi::trace::resolve_run_id_collectively( + MPI_COMM_WORLD, + std::optional{rank == 0 ? "rank-zero" : "rank-one"})); + } catch (std::runtime_error const&) { + failed = true; + } + REQUIRE(failed); +#else + SUCCEED("collective run-ID resolution is compiled out with tracing"); +#endif +} + +TEST_CASE("trace run ID fallback is common and explicit IDs stay deterministic", + "[mpi][trace][run-id]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE(size == 2); + +#if KAHIP_ENABLE_MPI_TRACE + auto const generated = parhip::mpi::trace::resolve_run_id_collectively( + MPI_COMM_WORLD, std::nullopt); + REQUIRE(!generated.empty()); + + auto root_length = rank == 0 ? static_cast(generated.size()) : 0; + REQUIRE(MPI_Bcast(&root_length, 1, MPI_INT, 0, MPI_COMM_WORLD) == + MPI_SUCCESS); + auto root_value = std::string(static_cast(root_length), '\0'); + if (rank == 0) { + root_value = generated; + } + REQUIRE(MPI_Bcast(root_value.data(), root_length, MPI_CHAR, 0, + MPI_COMM_WORLD) == MPI_SUCCESS); + REQUIRE(generated == root_value); + + auto const explicit_id = parhip::mpi::trace::resolve_run_id_collectively( + MPI_COMM_WORLD, std::optional{"oracle-fixture"}); + REQUIRE(explicit_id == "oracle-fixture"); +#else + SUCCEED("collective run-ID resolution is compiled out with tracing"); +#endif +} + +TEST_CASE( + "projection uses two dense exchanges and correlates stable request IDs", + "[mpi][projection]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE(size == 2); + + parhip::parallel_graph_access finer{MPI_COMM_WORLD}; + parhip::parallel_graph_access coarser{MPI_COMM_WORLD}; + auto const coarse_labels = rank == 0 + ? std::array{100, 101} + : std::array{202, 203}; + build_edgeless_graph(finer, rank, {0, 0}); + build_edgeless_graph(coarser, rank, coarse_labels); + + finer.allocate_node_to_cnode(); + if (rank == 0) { + // Fine-node order and coarse-node order disagree. A reply sorted by its + // stable request ID therefore cannot be applied by arrival position. + finer.setCNode(0, 3); + finer.setCNode(1, 2); + } else { + finer.setCNode(0, 1); + finer.setCNode(1, 0); + } + + parhip::mpi::trace::reset(); + parhip::mpi::trace::set_active(true); + KAHIP_MPI_TRACE_SET_HIERARCHY(7, 3, parhip::mpi::trace::epoch::projection); + { + protocol_probe::activation probe{}; + parhip::parallel_projection{}.parallel_project(MPI_COMM_WORLD, finer, + coarser); + } + require_callback_observation_is_safe(0); + + auto const expected = rank == 0 ? std::array{203, 202} + : std::array{101, 100}; + REQUIRE(finer.getNodeLabel(0) == expected[0]); + REQUIRE(finer.getNodeLabel(1) == expected[1]); + CAPTURE(protocol_probe::all_to_all_v_calls, + protocol_probe::all_to_all_v_c_calls, protocol_probe::isend_calls, + protocol_probe::probe_calls); + REQUIRE(protocol_probe::dense_payload_collective_calls() == 2); + REQUIRE(protocol_probe::isend_calls == 0); + REQUIRE(protocol_probe::probe_calls == 0); + +#if KAHIP_ENABLE_MPI_TRACE + auto const expected_trace = rank == 0 + ? std::string{ + "kahip-mpi-trace-v3 upstream=" + "5935f349f65f1788a9b68fcf6d853e698d86956d\n" + "projection-request cycle=7 level=3 epoch=projection iteration=0 round=0 " + "global=2 owner=1 requester=0 receiver=1 key=request:1 " + "requester=0 owner=1\n" + "projection-request cycle=7 level=3 epoch=projection iteration=0 round=0 " + "global=3 owner=1 requester=0 receiver=1 key=request:0 " + "requester=0 owner=1\n" + "projection-reply cycle=7 level=3 epoch=projection iteration=0 round=0 " + "global=0 owner=0 requester=1 receiver=1 key=request:3 " + "requester=1 owner=0 label=100\n" + "projection-reply cycle=7 level=3 epoch=projection iteration=0 round=0 " + "global=1 owner=0 requester=1 receiver=1 key=request:2 " + "requester=1 owner=0 label=101\n"} + : std::string{ + "kahip-mpi-trace-v3 upstream=" + "5935f349f65f1788a9b68fcf6d853e698d86956d\n" + "projection-request cycle=7 level=3 epoch=projection iteration=0 round=0 " + "global=0 owner=0 requester=1 receiver=0 key=request:3 " + "requester=1 owner=0\n" + "projection-request cycle=7 level=3 epoch=projection iteration=0 round=0 " + "global=1 owner=0 requester=1 receiver=0 key=request:2 " + "requester=1 owner=0\n" + "projection-reply cycle=7 level=3 epoch=projection iteration=0 round=0 " + "global=2 owner=1 requester=0 receiver=0 key=request:1 " + "requester=0 owner=1 label=202\n" + "projection-reply cycle=7 level=3 epoch=projection iteration=0 round=0 " + "global=3 owner=1 requester=0 receiver=0 key=request:0 " + "requester=0 owner=1 label=203\n"}; + REQUIRE(parhip::mpi::trace::canonical_text(parhip::mpi::trace::snapshot()) == + expected_trace); +#else + REQUIRE(parhip::mpi::trace::snapshot().empty()); +#endif +} + +TEST_CASE( + "projection rejects an empty-payload coarse-count mismatch collectively", + "[mpi][projection][failure][domain]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + parhip::parallel_graph_access finer{MPI_COMM_WORLD}; + parhip::parallel_graph_access coarser{MPI_COMM_WORLD}; + build_projection_finer(finer, rank, size, std::array{}); + build_empty_graph_with_global_count( + coarser, rank == 0 ? parhip::NodeID{2} : parhip::NodeID{3}, size); + + { + protocol_probe::activation probe{}; + require_collective_validation_failure( + [&] { + parhip::parallel_projection{}.parallel_project(MPI_COMM_WORLD, finer, + coarser); + }, + "projection coarse node count agreement failed", size); + } + require_callback_observation_is_safe(0); + REQUIRE(protocol_probe::dense_payload_collective_calls() == 0); +} + +TEST_CASE("zero-node projection performs two empty dense exchanges", + "[mpi][projection][zero]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + parhip::parallel_graph_access finer{MPI_COMM_WORLD}; + parhip::parallel_graph_access coarser{MPI_COMM_WORLD}; + build_projection_finer(finer, rank, size, std::array{}); + build_projection_coarser(coarser, rank, size, 0); + + { + protocol_probe::activation probe{}; + parhip::parallel_projection{}.parallel_project(MPI_COMM_WORLD, finer, + coarser); + } + require_callback_observation_is_safe(0); + + REQUIRE(finer.number_of_local_nodes() == 0); + REQUIRE(protocol_probe::dense_payload_collective_calls() == 2); + REQUIRE(protocol_probe::isend_calls == 0); + REQUIRE(protocol_probe::probe_calls == 0); + REQUIRE(protocol_probe::recv_calls == 0); +} + +TEST_CASE("projection rejects a tail coarse node before exchanging or mutating", + "[mpi][projection][failure][domain]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + auto const coarse_nodes = rank == 0 ? std::array{5, 0} + : rank == 1 ? std::array{2, 3} + : std::array{4, 4}; + parhip::parallel_graph_access finer{MPI_COMM_WORLD}; + parhip::parallel_graph_access coarser{MPI_COMM_WORLD}; + build_projection_finer(finer, rank, size, coarse_nodes); + build_projection_coarser(coarser, rank, size, 5); + + { + protocol_probe::activation probe{}; + require_collective_validation_failure( + [&] { + parhip::parallel_projection{}.parallel_project(MPI_COMM_WORLD, finer, + coarser); + }, + "projection local coarse-node validation failed", size); + } + require_callback_observation_is_safe(0); + + require_projection_labels_unchanged<2>(finer, rank); + REQUIRE(protocol_probe::dense_payload_collective_calls() == 0); +} + +TEST_CASE("projection routes an uneven coarse domain by exact ownership", + "[mpi][projection][ownership]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + auto const coarse_nodes = rank == 0 ? std::array{0, 4} + : rank == 1 ? std::array{2, 1} + : std::array{4, 3}; + parhip::parallel_graph_access finer{MPI_COMM_WORLD}; + parhip::parallel_graph_access coarser{MPI_COMM_WORLD}; + build_projection_finer(finer, rank, size, coarse_nodes); + build_projection_coarser(coarser, rank, size, 5); + + { + protocol_probe::activation probe{}; + parhip::parallel_projection{}.parallel_project(MPI_COMM_WORLD, finer, + coarser); + } + require_callback_observation_is_safe(0); + + for (std::size_t index = 0; index < coarse_nodes.size(); ++index) { + REQUIRE(finer.getNodeLabel(static_cast(index)) == + parhip::NodeID{100} + coarse_nodes[index]); + } + REQUIRE(protocol_probe::dense_payload_collective_calls() == 2); +} + +TEST_CASE( + "projection request corruption fails before replies and preserves labels", + "[mpi][projection][failure][receive]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 2) { + return; + } + + auto const coarse_nodes = rank == 0 ? std::array{0, 2} + : std::array{2, 0}; + parhip::parallel_graph_access finer{MPI_COMM_WORLD}; + parhip::parallel_graph_access coarser{MPI_COMM_WORLD}; + build_projection_finer(finer, rank, size, coarse_nodes); + build_projection_coarser(coarser, rank, size, 4); + + parhip::mpi::trace::reset(); + parhip::mpi::trace::set_active(true); + { + protocol_probe::activation probe{ + protocol_probe::receive_mutation::projection_request_wrong_owner, 1}; + require_collective_validation_failure( + [&] { + parhip::parallel_projection{}.parallel_project(MPI_COMM_WORLD, finer, + coarser); + }, + "projection request received validation failed", size); + } + require_callback_observation_is_safe(1); + + require_projection_labels_unchanged<2>(finer, rank); + REQUIRE(protocol_probe::dense_payload_collective_calls() == 1); + REQUIRE(parhip::mpi::trace::snapshot().empty()); + parhip::mpi::trace::set_active(false); +} + +TEST_CASE("projection reply corruption fails transactionally", + "[mpi][projection][failure][receive]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 2) { + return; + } + + auto const coarse_nodes = rank == 0 ? std::array{0, 2} + : std::array{2, 0}; + parhip::parallel_graph_access finer{MPI_COMM_WORLD}; + parhip::parallel_graph_access coarser{MPI_COMM_WORLD}; + build_projection_finer(finer, rank, size, coarse_nodes); + build_projection_coarser(coarser, rank, size, 4); + + parhip::mpi::trace::reset(); + parhip::mpi::trace::set_active(true); + { + protocol_probe::activation probe{ + protocol_probe::receive_mutation::projection_reply_wrong_coarse_id, 2}; + require_collective_validation_failure( + [&] { + parhip::parallel_projection{}.parallel_project(MPI_COMM_WORLD, finer, + coarser); + }, + "projection reply received validation failed", size); + } + require_callback_observation_is_safe(1); + + require_projection_labels_unchanged<2>(finer, rank); + REQUIRE(protocol_probe::dense_payload_collective_calls() == 2); + REQUIRE(parhip::mpi::trace::snapshot().empty()); + parhip::mpi::trace::set_active(false); +} + +TEST_CASE("projection rejects duplicate replies without partial label writes", + "[mpi][projection][failure][receive][duplicate]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 2) { + return; + } + + auto const coarse_nodes = rank == 0 ? std::array{2, 3} + : std::array{0, 1}; + parhip::parallel_graph_access finer{MPI_COMM_WORLD}; + parhip::parallel_graph_access coarser{MPI_COMM_WORLD}; + build_projection_finer(finer, rank, size, coarse_nodes); + build_projection_coarser(coarser, rank, size, 4); + + parhip::mpi::trace::reset(); + parhip::mpi::trace::set_active(true); + { + protocol_probe::activation probe{ + protocol_probe::receive_mutation::projection_reply_duplicate_request, + 2}; + require_collective_validation_failure( + [&] { + parhip::parallel_projection{}.parallel_project(MPI_COMM_WORLD, finer, + coarser); + }, + "projection reply received validation failed", size); + } + require_callback_observation_is_safe(1); + + require_projection_labels_unchanged<2>(finer, rank); + REQUIRE(protocol_probe::dense_payload_collective_calls() == 2); + REQUIRE(parhip::mpi::trace::snapshot().empty()); + parhip::mpi::trace::set_active(false); +} + +TEST_CASE("block-down accepts an identical same-sender duplicate", + "[mpi][block-propagation][owner][duplicate]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + parhip::parallel_graph_access finer{MPI_COMM_WORLD}; + parhip::parallel_graph_access coarser{MPI_COMM_WORLD}; + build_block_finer(finer, rank, size); + build_block_coarser(coarser, rank, size); + finer.allocate_node_to_cnode(); + for (parhip::NodeID node = 0; node < 4; ++node) { + finer.setCNode(node, rank == 0 && node == 1 ? 0 : node); + finer.setSecondPartitionIndex(node, + rank == 0 && node == 1 ? 10 : 10 + node); + } + + parhip::PPartitionConfig config{}; + config.k = 14; + parhip::parallel_block_down_propagation{}.propagate_block_down( + MPI_COMM_WORLD, config, finer, coarser); + + for (parhip::NodeID node = 0; node < coarser.number_of_local_nodes(); + ++node) { + auto const global = coarser.getGlobalID(node); + REQUIRE(coarser.getSecondPartitionIndex(node) == 10 + global); + } +} + +TEST_CASE( + "block-down rejects an empty-payload coarse-count mismatch collectively", + "[mpi][block-propagation][owner][failure][domain]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + parhip::parallel_graph_access finer{MPI_COMM_WORLD}; + parhip::parallel_graph_access coarser{MPI_COMM_WORLD}; + build_empty_graph_with_global_count(finer, 0, size); + build_empty_graph_with_global_count( + coarser, rank == 0 ? parhip::NodeID{2} : parhip::NodeID{3}, size); + finer.allocate_node_to_cnode(); + + parhip::PPartitionConfig config{}; + config.k = 1; + require_collective_validation_failure( + [&] { + parhip::parallel_block_down_propagation{}.propagate_block_down( + MPI_COMM_WORLD, config, finer, coarser); + }, + "block-down coarse-node count agreement failed", size); + REQUIRE(coarser.number_of_local_nodes() == 0); +} + +TEST_CASE( + "block-down rejects a rank-local conflicting coarse block collectively", + "[mpi][block-propagation][owner][failure]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + parhip::parallel_graph_access finer{MPI_COMM_WORLD}; + parhip::parallel_graph_access coarser{MPI_COMM_WORLD}; + build_block_finer(finer, rank, size); + build_block_coarser(coarser, rank, size); + finer.allocate_node_to_cnode(); + finer.setCNode(0, 0); + finer.setCNode(1, 0); + finer.setCNode(2, 2); + finer.setCNode(3, 3); + finer.setSecondPartitionIndex(0, 10); + finer.setSecondPartitionIndex(1, rank == 0 ? 11 : 10); + finer.setSecondPartitionIndex(2, 12); + finer.setSecondPartitionIndex(3, 13); + + parhip::PPartitionConfig config{}; + config.k = 14; + require_collective_validation_failure( + [&] { + parhip::parallel_block_down_propagation{}.propagate_block_down( + MPI_COMM_WORLD, config, finer, coarser); + }, + "block-down local update validation failed", size); +} + +TEST_CASE("block-down rejects a tail-padding coarse ID collectively", + "[mpi][block-propagation][owner][failure]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + parhip::parallel_graph_access finer{MPI_COMM_WORLD}; + parhip::parallel_graph_access coarser{MPI_COMM_WORLD}; + build_block_finer(finer, rank, size); + build_block_coarser(coarser, rank, size); + finer.allocate_node_to_cnode(); + finer.setCNode(0, 0); + finer.setCNode(1, rank == 0 ? 4 : 1); + finer.setCNode(2, 2); + finer.setCNode(3, 3); + finer.setSecondPartitionIndex(0, 10); + finer.setSecondPartitionIndex(1, 11); + finer.setSecondPartitionIndex(2, 12); + finer.setSecondPartitionIndex(3, 13); + + parhip::PPartitionConfig config{}; + config.k = 14; + require_collective_validation_failure( + [&] { + parhip::parallel_block_down_propagation{}.propagate_block_down( + MPI_COMM_WORLD, config, finer, coarser); + }, + "block-down local update validation failed", size); + REQUIRE(coarser.number_of_local_edges() == 0); +} + +TEST_CASE( + "block-down rejects a cross-rank conflicting coarse block collectively", + "[mpi][block-propagation][owner][failure]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + parhip::parallel_graph_access finer{MPI_COMM_WORLD}; + parhip::parallel_graph_access coarser{MPI_COMM_WORLD}; + build_block_finer(finer, rank, size); + build_block_coarser(coarser, rank, size); + finer.allocate_node_to_cnode(); + finer.setCNode(0, 0); + finer.setCNode(1, 1); + finer.setCNode(2, 2); + finer.setCNode(3, 3); + finer.setSecondPartitionIndex(0, rank == 0 ? 11 : 10); + finer.setSecondPartitionIndex(1, 11); + finer.setSecondPartitionIndex(2, 12); + finer.setSecondPartitionIndex(3, 13); + + parhip::PPartitionConfig config{}; + config.k = 14; + require_collective_validation_failure( + [&] { + parhip::parallel_block_down_propagation{}.propagate_block_down( + MPI_COMM_WORLD, config, finer, coarser); + }, + "block-down dense received validation failed", size); +} + +TEST_CASE("block-down rejects a missing coarse block collectively", + "[mpi][block-propagation][owner][failure]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 3) { + return; + } + + parhip::parallel_graph_access finer{MPI_COMM_WORLD}; + parhip::parallel_graph_access coarser{MPI_COMM_WORLD}; + build_block_finer(finer, rank, size); + build_block_coarser(coarser, rank, size); + finer.allocate_node_to_cnode(); + for (parhip::NodeID node = 0; node < 4; ++node) { + finer.setCNode(node, 0); + finer.setSecondPartitionIndex(node, 10); + } + + parhip::PPartitionConfig config{}; + config.k = 14; + require_collective_validation_failure( + [&] { + parhip::parallel_block_down_propagation{}.propagate_block_down( + MPI_COMM_WORLD, config, finer, coarser); + }, + "block-down dense received validation failed", size); +} + +TEST_CASE( + "complete-graph distribution preserves root vcycle blocks while " + "replicating structure", + "[mpi][complete-graph][block-propagation][vcycle][distribution]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + if (size != 2) { + return; + } + + parhip::complete_graph_access graph{MPI_COMM_WORLD}; + if (rank == static_cast(parhip::ROOT)) { + graph.start_construction(3, 4, 3, 4, false); + graph.set_range(0, 3); + + auto const node_0 = graph.new_node(); + graph.setNodeWeight(node_0, 5); + graph.setSecondPartitionIndex(node_0, 2); + auto const edge_0 = graph.new_edge(node_0, 1); + graph.setEdgeWeight(edge_0, 7); + + auto const node_1 = graph.new_node(); + graph.setNodeWeight(node_1, 11); + graph.setSecondPartitionIndex(node_1, 3); + auto const edge_1 = graph.new_edge(node_1, 0); + graph.setEdgeWeight(edge_1, 7); + auto const edge_2 = graph.new_edge(node_1, 2); + graph.setEdgeWeight(edge_2, 13); + + auto const node_2 = graph.new_node(); + graph.setNodeWeight(node_2, 17); + graph.setSecondPartitionIndex(node_2, 2); + auto const edge_3 = graph.new_edge(node_2, 1); + graph.setEdgeWeight(edge_3, 13); + graph.finish_construction(); + } + + parhip::PPartitionConfig config{}; + parhip::mpi_tools{}.distribute_local_graph(MPI_COMM_WORLD, config, graph); + + auto structure_is_exact = + graph.number_of_local_nodes() == 3 && + graph.number_of_local_edges() == 4 && graph.getNodeWeight(0) == 5 && + graph.getNodeWeight(1) == 11 && graph.getNodeWeight(2) == 17 && + graph.getNodeDegree(0) == 1 && graph.getNodeDegree(1) == 2 && + graph.getNodeDegree(2) == 1 && + graph.getEdgeTarget(graph.get_first_edge(0)) == 1 && + graph.getEdgeWeight(graph.get_first_edge(0)) == 7 && + graph.getEdgeTarget(graph.get_first_edge(1)) == 0 && + graph.getEdgeWeight(graph.get_first_edge(1)) == 7 && + graph.getEdgeTarget(graph.get_first_edge(1) + 1) == 2 && + graph.getEdgeWeight(graph.get_first_edge(1) + 1) == 13 && + graph.getEdgeTarget(graph.get_first_edge(2)) == 1 && + graph.getEdgeWeight(graph.get_first_edge(2)) == 13; + auto root_vcycle_blocks_are_exact = rank != static_cast(parhip::ROOT) || + (graph.getSecondPartitionIndex(0) == 2 && + graph.getSecondPartitionIndex(1) == 3 && + graph.getSecondPartitionIndex(2) == 2); + + int local_structure = structure_is_exact ? 1 : 0; + int all_structure = 0; + REQUIRE(MPI_Allreduce(&local_structure, &all_structure, 1, MPI_INT, MPI_MIN, + MPI_COMM_WORLD) == MPI_SUCCESS); + int local_blocks = root_vcycle_blocks_are_exact ? 1 : 0; + int all_blocks = 0; + REQUIRE(MPI_Allreduce(&local_blocks, &all_blocks, 1, MPI_INT, MPI_MIN, + MPI_COMM_WORLD) == MPI_SUCCESS); + REQUIRE(all_structure == 1); + REQUIRE(all_blocks == 1); +} + +TEST_CASE( + "complete-graph collection uses two compact dense transactions and " + "canonical rank order", + "[mpi][complete-graph][collection]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE(size >= 1); + REQUIRE(size <= 5); + + auto const local_nodes = static_cast(rank + 1); + auto const first = static_cast(rank * (rank + 1) / 2); + auto const global_nodes = static_cast(size * (size + 1) / 2); + + parhip::parallel_graph_access distributed{MPI_COMM_WORLD}; + distributed.start_construction(local_nodes, local_nodes, global_nodes, + global_nodes, false); + distributed.set_range(first, first + local_nodes - 1); + auto ranges = std::vector(static_cast(size) + 1); + for (int pe = 0; pe <= size; ++pe) { + ranges[static_cast(pe)] = + static_cast(pe * (pe + 1) / 2); + } + distributed.set_range_array(ranges); + for (parhip::NodeID index = 0; index < local_nodes; ++index) { + auto const global = first + index; + auto const node = distributed.new_node(); + distributed.setNodeWeight(node, parhip::NodeWeight{100} + global); + distributed.setSecondPartitionIndex(node, parhip::NodeID{200} + global); + auto const edge = distributed.new_edge(node, global); + distributed.setEdgeWeight(edge, parhip::EdgeWeight{300} + global); + } + distributed.finish_construction(); + + parhip::complete_graph_access complete{MPI_COMM_WORLD}; + parhip::PPartitionConfig config{}; + { + protocol_probe::activation probe{}; + parhip::mpi_tools{}.collect_parallel_graph_to_local_graph( + MPI_COMM_WORLD, config, distributed, complete); + } + require_callback_observation_is_safe(0); + + CAPTURE(protocol_probe::all_to_all_v_calls, + protocol_probe::all_to_all_v_c_calls, protocol_probe::isend_calls, + protocol_probe::probe_calls, protocol_probe::recv_calls); + REQUIRE(protocol_probe::dense_payload_collective_calls() == 2); + REQUIRE(protocol_probe::isend_calls == 0); + REQUIRE(protocol_probe::probe_calls == 0); + REQUIRE(protocol_probe::recv_calls == 0); + + auto root_is_exact = true; + if (rank == static_cast(parhip::ROOT)) { + root_is_exact = complete.number_of_local_nodes() == global_nodes && + complete.number_of_local_edges() == global_nodes; + for (parhip::NodeID global = 0; root_is_exact && global < global_nodes; + ++global) { + auto const edge = complete.get_first_edge(global); + root_is_exact = + complete.getNodeWeight(global) == parhip::NodeWeight{100} + global && + complete.getSecondPartitionIndex(global) == + parhip::NodeID{200} + global && + complete.getNodeDegree(global) == 1 && + complete.getEdgeTarget(edge) == global && + complete.getEdgeWeight(edge) == parhip::EdgeWeight{300} + global; + } + } + auto local_exact = root_is_exact ? 1 : 0; + auto all_exact = 0; + REQUIRE(PMPI_Allreduce(&local_exact, &all_exact, 1, MPI_INT, MPI_MIN, + MPI_COMM_WORLD) == MPI_SUCCESS); + REQUIRE(all_exact == 1); +} + +TEST_CASE( + "complete-graph collection accepts empty and zero-local rank " + "segments", + "[mpi][complete-graph][collection][empty]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE(size >= 1); + REQUIRE(size <= 5); + + auto const local_nodes = + rank % 2 == 0 ? parhip::NodeID{0} : static_cast(rank); + auto first = parhip::NodeID{0}; + auto global_nodes = parhip::NodeID{0}; + for (int pe = 0; pe < size; ++pe) { + auto const pe_nodes = + pe % 2 == 0 ? parhip::NodeID{0} : static_cast(pe); + if (pe < rank) { + first += pe_nodes; + } + global_nodes += pe_nodes; + } + + parhip::parallel_graph_access distributed{MPI_COMM_WORLD}; + distributed.start_construction(local_nodes, 0, global_nodes, 0, false); + distributed.set_range(first, + local_nodes == 0 ? first : first + local_nodes - 1); + auto ranges = std::vector(static_cast(size) + 1, + parhip::NodeID{0}); + auto boundary = parhip::NodeID{0}; + for (int pe = 0; pe < size; ++pe) { + ranges[static_cast(pe)] = boundary; + if (pe % 2 != 0) { + boundary += static_cast(pe); + } + } + ranges.back() = boundary; + distributed.set_range_array(ranges); + for (parhip::NodeID index = 0; index < local_nodes; ++index) { + auto const global = first + index; + auto const node = distributed.new_node(); + distributed.setNodeWeight(node, parhip::NodeWeight{700} + global); + distributed.setSecondPartitionIndex(node, parhip::NodeID{900} + global); + } + distributed.finish_construction(); + + parhip::complete_graph_access complete{MPI_COMM_WORLD}; + parhip::PPartitionConfig config{}; + { + protocol_probe::activation probe{}; + parhip::mpi_tools{}.collect_parallel_graph_to_local_graph( + MPI_COMM_WORLD, config, distributed, complete); + } + require_callback_observation_is_safe(0); + REQUIRE(protocol_probe::dense_payload_collective_calls() == 2); + REQUIRE(protocol_probe::isend_calls == 0); + REQUIRE(protocol_probe::recv_calls == 0); + + auto root_is_exact = true; + if (rank == static_cast(parhip::ROOT)) { + root_is_exact = complete.number_of_local_nodes() == global_nodes && + complete.number_of_local_edges() == 0; + for (parhip::NodeID global = 0; root_is_exact && global < global_nodes; + ++global) { + root_is_exact = + complete.getNodeWeight(global) == parhip::NodeWeight{700} + global && + complete.getSecondPartitionIndex(global) == + parhip::NodeID{900} + global && + complete.getNodeDegree(global) == 0; + } + } + auto local_exact = root_is_exact ? 1 : 0; + auto all_exact = 0; + REQUIRE(PMPI_Allreduce(&local_exact, &all_exact, 1, MPI_INT, MPI_MIN, + MPI_COMM_WORLD) == MPI_SUCCESS); + REQUIRE(all_exact == 1); +} + +TEST_CASE("complete-graph wire records retain compact exact MPI extents", + "[mpi][complete-graph][wire]") { + using node_record = parhip::mpi_tools_detail::complete_graph_node_record; + using edge_record = parhip::mpi_tools_detail::complete_graph_edge_record; + + STATIC_REQUIRE(sizeof(node_record) == 4 * sizeof(std::uint64_t)); + STATIC_REQUIRE(sizeof(edge_record) == 2 * sizeof(std::uint64_t)); + + auto node_datatype = + parhip::mpi::make_mpi_datatype(MPI_COMM_WORLD); + auto edge_datatype = + parhip::mpi::make_mpi_datatype(MPI_COMM_WORLD); + auto node_lower_bound = MPI_Aint{-1}; + auto node_extent = MPI_Aint{-1}; + auto edge_lower_bound = MPI_Aint{-1}; + auto edge_extent = MPI_Aint{-1}; + REQUIRE(MPI_Type_get_extent(node_datatype.native_handle(), &node_lower_bound, + &node_extent) == MPI_SUCCESS); + REQUIRE(MPI_Type_get_extent(edge_datatype.native_handle(), &edge_lower_bound, + &edge_extent) == MPI_SUCCESS); + REQUIRE(node_lower_bound == 0); + REQUIRE(node_extent == static_cast(sizeof(node_record))); + REQUIRE(edge_lower_bound == 0); + REQUIRE(edge_extent == static_cast(sizeof(edge_record))); +} + +TEST_CASE( + "complete-graph distribution covers rank-one through rank-five " + "shapes", + "[mpi][complete-graph][distribution]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE(size >= 1); + REQUIRE(size <= 5); + + auto const node_count = static_cast(size + 1); + parhip::complete_graph_access graph{MPI_COMM_WORLD}; + if (rank == static_cast(parhip::ROOT)) { + graph.start_construction(node_count, node_count, node_count, node_count, + false); + graph.set_range(0, node_count); + for (parhip::NodeID global = 0; global < node_count; ++global) { + auto const node = graph.new_node(); + graph.setNodeWeight(node, parhip::NodeWeight{20} + global); + graph.setSecondPartitionIndex(node, parhip::NodeID{40} + global); + auto const edge = graph.new_edge(node, global); + graph.setEdgeWeight(edge, parhip::EdgeWeight{60} + global); + } + graph.finish_construction(); + } + + parhip::PPartitionConfig config{}; + parhip::mpi_tools{}.distribute_local_graph(MPI_COMM_WORLD, config, graph); + + auto local_exact = graph.number_of_local_nodes() == node_count && + graph.number_of_local_edges() == node_count; + for (parhip::NodeID global = 0; local_exact && global < node_count; + ++global) { + auto const edge = graph.get_first_edge(global); + local_exact = + graph.getNodeWeight(global) == parhip::NodeWeight{20} + global && + graph.getNodeDegree(global) == 1 && + graph.getEdgeTarget(edge) == global && + graph.getEdgeWeight(edge) == parhip::EdgeWeight{60} + global; + if (rank == static_cast(parhip::ROOT)) { + local_exact = local_exact && graph.getSecondPartitionIndex(global) == + parhip::NodeID{40} + global; + } + } + auto local_exact_int = local_exact ? 1 : 0; + auto all_exact = 0; + REQUIRE(PMPI_Allreduce(&local_exact_int, &all_exact, 1, MPI_INT, MPI_MIN, + MPI_COMM_WORLD) == MPI_SUCCESS); + REQUIRE(all_exact == 1); +} + +TEST_CASE("empty complete-graph distribution remains empty on every rank", + "[mpi][complete-graph][distribution][empty]") { + int rank = 0; + int size = 0; + REQUIRE(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); + REQUIRE(MPI_Comm_size(MPI_COMM_WORLD, &size) == MPI_SUCCESS); + REQUIRE(size >= 1); + REQUIRE(size <= 5); + + parhip::complete_graph_access graph{MPI_COMM_WORLD}; + if (rank == static_cast(parhip::ROOT)) { + graph.start_construction(0, 0, 0, 0, false); + graph.set_range(0, 0); + graph.finish_construction(); + } + parhip::PPartitionConfig config{}; + parhip::mpi_tools{}.distribute_local_graph(MPI_COMM_WORLD, config, graph); + + auto local_empty = + graph.number_of_local_nodes() == 0 && graph.number_of_local_edges() == 0; + auto local_empty_int = local_empty ? 1 : 0; + auto all_empty = 0; + REQUIRE(PMPI_Allreduce(&local_empty_int, &all_empty, 1, MPI_INT, MPI_MIN, + MPI_COMM_WORLD) == MPI_SUCCESS); + REQUIRE(all_empty == 1); +} diff --git a/parallel/parallel_src/tests/scale/cube_scale_probe_core.h b/parallel/parallel_src/tests/scale/cube_scale_probe_core.h new file mode 100644 index 00000000..71ea3296 --- /dev/null +++ b/parallel/parallel_src/tests/scale/cube_scale_probe_core.h @@ -0,0 +1,416 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace parhip::scale_probe { +static_assert(std::numeric_limits::digits == 64); + +struct cube_counts final { + std::uint64_t vertices; + std::uint64_t undirected_edges; + std::uint64_t directed_edges; + + auto operator==(cube_counts const&) const -> bool = default; +}; + +namespace detail { +[[nodiscard]] constexpr auto checked_add(std::uint64_t left, + std::uint64_t right) noexcept + -> std::optional { + if (right > std::numeric_limits::max() - left) { + return std::nullopt; + } + return left + right; +} + +[[nodiscard]] constexpr auto checked_multiply(std::uint64_t left, + std::uint64_t right) noexcept + -> std::optional { + if (left != 0 && right > std::numeric_limits::max() / left) { + return std::nullopt; + } + return left * right; +} +} // namespace detail + +[[nodiscard]] constexpr auto counts_for_side(std::uint64_t side) noexcept + -> std::optional { + if (side == 0) { + return std::nullopt; + } + auto const square = detail::checked_multiply(side, side); + if (!square.has_value()) { + return std::nullopt; + } + auto const vertices = detail::checked_multiply(*square, side); + auto const per_axis = detail::checked_multiply(*square, side - 1); + if (!vertices.has_value() || !per_axis.has_value()) { + return std::nullopt; + } + auto const undirected = detail::checked_multiply(*per_axis, 3); + if (!undirected.has_value()) { + return std::nullopt; + } + auto const directed = detail::checked_multiply(*undirected, 2); + if (!directed.has_value()) { + return std::nullopt; + } + return cube_counts{.vertices = *vertices, + .undirected_edges = *undirected, + .directed_edges = *directed}; +} + +[[nodiscard]] constexpr auto balanced_boundary(std::uint64_t total, + std::uint32_t index, + std::uint32_t parts) noexcept + -> std::optional { + if (parts == 0 || index > parts) { + return std::nullopt; + } + auto const quotient = total / parts; + auto const remainder = total % parts; + auto const whole = detail::checked_multiply(quotient, index); + auto const fractional_numerator = detail::checked_multiply(remainder, index); + if (!whole.has_value() || !fractional_numerator.has_value()) { + return std::nullopt; + } + return detail::checked_add(*whole, *fractional_numerator / parts); +} + +[[nodiscard]] inline auto write_balanced_boundaries( + std::uint64_t total, + std::span boundaries) noexcept -> bool { + if (boundaries.size() < 2 || + boundaries.size() - 1 > + static_cast(std::numeric_limits::max())) { + return false; + } + auto const parts = static_cast(boundaries.size() - 1); + for (auto index = std::uint32_t{0};; ++index) { + auto const boundary = balanced_boundary(total, index, parts); + if (!boundary.has_value()) { + return false; + } + boundaries[index] = *boundary; + if (index == parts) { + break; + } + } + return true; +} + +[[nodiscard]] constexpr auto maximum_balanced_slice( + std::uint64_t total, + std::uint32_t parts) noexcept -> std::optional { + if (parts == 0) { + return std::nullopt; + } + auto const quotient = total / parts; + return quotient + (total % parts == 0 ? std::uint64_t{0} : std::uint64_t{1}); +} + +[[nodiscard]] constexpr auto exact_unit_weight_bound( + std::uint64_t vertices, + std::uint32_t blocks, + unsigned imbalance_percent) noexcept -> std::optional { + if (blocks == 0) { + return std::nullopt; + } + auto const quotient = vertices / blocks; + auto const ceiling = + quotient + (vertices % blocks == 0 ? std::uint64_t{0} : std::uint64_t{1}); + auto const factor = + detail::checked_add(100, static_cast(imbalance_percent)); + if (!factor.has_value()) { + return std::nullopt; + } + + // floor(ceiling * (100 + p) / 100), without forming the possibly + // overflowing product. This implementation is deliberately independent of + // the library helper whose public-bound behavior the probe is checking. + auto const whole_hundreds = ceiling / 100; + auto const remaining_hundredths = ceiling % 100; + auto const whole = detail::checked_multiply(whole_hundreds, *factor); + auto const fractional = + detail::checked_multiply(remaining_hundredths, *factor); + if (!whole.has_value() || !fractional.has_value()) { + return std::nullopt; + } + return detail::checked_add(*whole, *fractional / 100); +} + +struct cube_neighbor_list final { + std::array values{}; + std::uint8_t count{}; + + [[nodiscard]] constexpr auto span() const noexcept + -> std::span { + return {values.data(), count}; + } +}; + +[[nodiscard]] constexpr auto neighbors_for_vertex(std::uint64_t side, + std::uint64_t vertex) + -> std::optional { + auto const counts = counts_for_side(side); + if (!counts.has_value() || vertex >= counts->vertices) { + return std::nullopt; + } + auto const plane = side * side; + auto const z = vertex / plane; + auto const in_plane = vertex % plane; + auto const y = in_plane / side; + auto const x = in_plane % side; + auto result = cube_neighbor_list{}; + auto const append = [&result](std::uint64_t target) constexpr { + result.values[result.count++] = target; + }; + if (x != 0) { + append(vertex - 1); + } + if (x + 1 < side) { + append(vertex + 1); + } + if (y != 0) { + append(vertex - side); + } + if (y + 1 < side) { + append(vertex + side); + } + if (z != 0) { + append(vertex - plane); + } + if (z + 1 < side) { + append(vertex + plane); + } + std::ranges::sort( + std::span{result.values.data(), static_cast(result.count)}); + return result; +} + +struct local_cube_csr final { + std::uint64_t first_vertex{}; + std::uint64_t vertex_end{}; + std::vector offsets; + std::vector targets; +}; + +[[nodiscard]] inline auto build_local_cube_csr(std::uint64_t side, + std::uint64_t first, + std::uint64_t end) + -> local_cube_csr { + auto const counts = counts_for_side(side); + if (!counts.has_value()) { + throw std::overflow_error{"cube counts are outside the uint64 domain"}; + } + if (first > end) { + throw std::invalid_argument{"cube slice start exceeds its end"}; + } + if (end > counts->vertices) { + throw std::out_of_range{"cube slice exceeds the vertex domain"}; + } + auto const local_vertices_u64 = end - first; + if (!std::in_range(local_vertices_u64)) { + throw std::length_error{"cube slice exceeds the size_t domain"}; + } + auto const local_vertices = static_cast(local_vertices_u64); + if (local_vertices == std::numeric_limits::max() || + local_vertices + 1 > std::vector{}.max_size()) { + throw std::length_error{"cube offsets exceed vector capacity"}; + } + + auto local_edges = std::uint64_t{0}; + for (auto vertex = first; vertex < end; ++vertex) { + auto const adjacent = neighbors_for_vertex(side, vertex); + if (!adjacent.has_value()) { + throw std::logic_error{"valid cube slice produced an invalid vertex"}; + } + auto const next = detail::checked_add(local_edges, adjacent->count); + if (!next.has_value()) { + throw std::overflow_error{"local cube edge count overflow"}; + } + local_edges = *next; + } + if (!std::in_range(local_edges) || + static_cast(local_edges) > + std::vector{}.max_size()) { + throw std::length_error{"cube targets exceed vector capacity"}; + } + + auto result = local_cube_csr{ + .first_vertex = first, + .vertex_end = end, + .offsets = std::vector(local_vertices + 1), + .targets = std::vector( + static_cast(local_edges)), + }; + auto target_index = std::size_t{0}; + for (auto local = std::size_t{0}; local < local_vertices; ++local) { + auto const adjacent = neighbors_for_vertex(side, first + local); + if (!adjacent.has_value()) { + throw std::logic_error{"valid cube slice produced an invalid vertex"}; + } + result.offsets[local] = static_cast(target_index); + for (auto target : adjacent->span()) { + result.targets[target_index++] = static_cast(target); + } + } + result.offsets.back() = static_cast(target_index); + if (target_index != result.targets.size()) { + throw std::logic_error{"cube CSR passes disagree"}; + } + return result; +} + +struct digest_lanes final { + std::array values{}; + + auto operator==(digest_lanes const&) const -> bool = default; + + constexpr auto operator^=(digest_lanes const& other) noexcept + -> digest_lanes& { + for (auto lane = std::size_t{0}; lane < values.size(); ++lane) { + values[lane] ^= other.values[lane]; + } + return *this; + } +}; + +[[nodiscard]] constexpr auto operator^(digest_lanes left, + digest_lanes const& right) noexcept + -> digest_lanes { + left ^= right; + return left; +} + +// Digest schema v1 hashes semantic integers only. Each record starts from one +// of four published lane seeds XORed with its domain and the versioned golden +// ratio tag. Ordered fields are tagged by their one-based ordinal, finalized +// with SplitMix64, and folded into the lane state in order. Complete record +// digests are combined with XOR, making the aggregate independent of the MPI +// decomposition while retaining field order inside each semantic record. No +// object representation or std::hash participates. +enum class digest_domain : std::uint64_t { + cube_vertex = 0x637562655f767478ULL, + cube_arc = 0x637562655f617263ULL, + partition_map = 0x706172745f6d6170ULL, + profile_sequence = 0x70726f66696c655fULL, +}; + +namespace detail { +inline constexpr auto digest_version = std::uint64_t{1}; +inline constexpr auto golden = std::uint64_t{0x9e3779b97f4a7c15ULL}; +inline constexpr auto lane_seeds = std::array{ + std::uint64_t{0x243f6a8885a308d3ULL}, + std::uint64_t{0x13198a2e03707344ULL}, + std::uint64_t{0xa4093822299f31d0ULL}, + std::uint64_t{0x082efa98ec4e6c89ULL}, +}; + +[[nodiscard]] constexpr auto splitmix64(std::uint64_t value) noexcept + -> std::uint64_t { + value += golden; + value = (value ^ (value >> 30)) * 0xbf58476d1ce4e5b9ULL; + value = (value ^ (value >> 27)) * 0x94d049bb133111ebULL; + return value ^ (value >> 31); +} +} // namespace detail + +template +[[nodiscard]] constexpr auto digest_record( + digest_domain domain, + std::array const& fields) noexcept + -> digest_lanes { + auto result = digest_lanes{}; + for (auto lane = std::size_t{0}; lane < result.values.size(); ++lane) { + auto hash = detail::splitmix64(detail::lane_seeds[lane] ^ + static_cast(domain) ^ + (detail::golden * detail::digest_version)); + for (auto field = std::size_t{0}; field < fields.size(); ++field) { + auto const tagged = + fields[field] + + detail::golden * (static_cast(field) + 1); + hash = detail::splitmix64(hash ^ detail::splitmix64(tagged)); + } + result.values[lane] = hash; + } + return result; +} + +[[nodiscard]] inline auto graph_digest(std::uint64_t side, + std::uint64_t first, + std::uint64_t end) -> digest_lanes { + auto const counts = counts_for_side(side); + if (!counts.has_value() || first > end || end > counts->vertices) { + throw std::out_of_range{"graph digest slice is outside the cube"}; + } + auto result = digest_lanes{}; + for (auto vertex = first; vertex < end; ++vertex) { + auto const adjacent = *neighbors_for_vertex(side, vertex); + // Vertex tuple: side, global ID, directed degree, unit node weight. + result ^= digest_record( + digest_domain::cube_vertex, + std::array{side, vertex, adjacent.count, 1}); + for (auto ordinal = std::size_t{0}; ordinal < adjacent.count; ++ordinal) { + // Arc tuple: side, source, sorted-adjacency ordinal, target, unit edge + // weight. Both directions are present because this hashes the CSR. + result ^= + digest_record(digest_domain::cube_arc, + std::array{ + side, vertex, static_cast(ordinal), + adjacent.values[ordinal], 1}); + } + } + return result; +} + +template +[[nodiscard]] auto partition_digest(std::uint64_t side, + std::uint64_t first, + std::span