Program Listing for File GenerateMetisAssignments.hpp

Return to documentation for file (include/netuit/assign/GenerateMetisAssignments.hpp)

#pragma once
#ifndef NETUIT_ASSIGN_GENERATEMETISASSIGNMENTS_HPP_INCLUDE
#define NETUIT_ASSIGN_GENERATEMETISASSIGNMENTS_HPP_INCLUDE

#include <functional>
#include <numeric>
#include <stddef.h>
#include <utility>
#include <vector>

#ifndef __EMSCRIPTEN__
#include <metis.h>
#endif

#include "../../uitsl/debug/audit_cast.hpp"
#include "../../uitsl/debug/EnumeratedFunctor.hpp"
#include "../../uitsl/debug/uitsl_assert.hpp"
#include "../../uitsl/mpi/mpi_init_utils.hpp"
#include "../../uitsl/parallel/thread_utils.hpp"

#include "../topology/Topology.hpp"

namespace netuit {

std::vector<int32_t> PartitionMetis(
  const size_t num_parts, const netuit::Topology& topology
) {

  uitsl_assert(
    num_parts <= topology.GetSize(),
    num_parts << topology.GetSize()
  );

  // set up result vector
  std::vector<int32_t> result( topology.GetSize(), 0 );

  // the trivial no-split partition crashes METIS, so return before METIS call
  if ( num_parts == 1 ) return result;

  // manual override for trivial one node per partition
  // ensures that each partition gets a node
  if ( num_parts == topology.GetSize() ) {
    std::iota( std::begin( result ), std::end( result ), 0 );
    return result;
  }

  #ifndef __EMSCRIPTEN__
  // set up variables
  int32_t nodes = topology.GetSize();
  int32_t n_cons = 1;
  int32_t parts = uitsl::audit_cast<int32_t>( num_parts );
  int32_t objval;

  // get topology as CSR
  auto [xadj, adjacency] = topology.AsCSR();

  // use default options
  int32_t options[METIS_NOPTIONS];
  METIS_SetDefaultOptions(options);

  // call partitioning algorithm
  const int status = METIS_PartGraphKway(
    &nodes, // idx_t *nvtxs: number of vertices in the graph
    &n_cons, // idx_t *ncon: number of balancing constraints.
    xadj.data(), // idx_t *xadj: array of node indexes into adjacency[]
    adjacency.data(), // idx_t *adjncy:  array of adjacenct nodes for every node
    nullptr, // idx_t *vwgt: weights of nodes
    nullptr, // idx_t *vsize: size of nodes for total comunication value
    nullptr, // idx_t *adjwgt: weights of edges
    &parts, // idx_t *nparts: number of parts to partition the graph into
    nullptr, // real_t *tpwgts: weight for each partition and constraint
    nullptr, // real_t ubvec: allowed load imbalance tolerance for each constrnt
    nullptr, // idx_t *options: array of options
    &objval, // idx_t *objvalL edge-cut or total comm volume of the solution
    result.data() // idx_t *part: partition vector of the graph
  );

  uitsl::metis::verify(status);
  #endif

  return result;
}

std::unordered_map<uitsl::proc_id_t, netuit::Topology> GetSubTopologies(
  const netuit::Topology& topo,
  const uitsl::EnumeratedFunctor<netuit::Topology::node_id_t, uitsl::proc_id_t>& assigner
) {
  std::unordered_map<uitsl::proc_id_t, netuit::Topology> subtopos;

  std::unordered_map<
    uitsl::proc_id_t,
    std::unordered_set<size_t>
  > node_map;
  for (size_t node_id = 0; node_id < assigner.GetSize(); ++node_id) {
    node_map[assigner(node_id)].insert(node_id);
  }

  // todo: only run for current process?
  for (const auto& [proc_id, nodes] : node_map) {
    // make subtopology
    subtopos[proc_id] = topo.GetSubTopology(nodes);
  }

  return subtopos;
}

// todo: rename
std::unordered_map<size_t, uitsl::thread_id_t> Shim(
  const std::unordered_map<uitsl::proc_id_t, netuit::Topology>& proc_map,
  const size_t threads_per_proc
) {
  std::unordered_map<size_t, uitsl::thread_id_t> ret;

  for (const auto& [proc_id, subtopo] : proc_map) {
    const auto thread_assign = PartitionMetis( threads_per_proc, subtopo );
    for (size_t i = 0; i < subtopo.GetSize(); ++i) {
      ret[subtopo.GetCanonicalNodeID(i)] = thread_assign[i];
    }
  }

  return ret;
}


std::pair<
  uitsl::EnumeratedFunctor<netuit::Topology::node_id_t, uitsl::proc_id_t>,
  uitsl::EnumeratedFunctor<netuit::Topology::node_id_t, uitsl::thread_id_t>
> GenerateMetisAssignments (
  const size_t num_procs,
  const size_t threads_per_proc,
  const netuit::Topology& topology
) {
  // make sure topology isn't empty
  if (topology.GetSize() == 0) return {};

  uitsl::EnumeratedFunctor<netuit::Topology::node_id_t, uitsl::proc_id_t>
    proc_assigner{ PartitionMetis(num_procs, topology) };

  uitsl::EnumeratedFunctor<netuit::Topology::node_id_t, uitsl::thread_id_t>
    thread_assigner{ Shim(
      GetSubTopologies(topology, proc_assigner),
      threads_per_proc
    ) };

  return std::pair{
    proc_assigner,
    thread_assigner
  };
}

std::pair<
  std::function<uitsl::proc_id_t(size_t)>,
  std::function<uitsl::thread_id_t(size_t)>
> GenerateMetisAssignmentFunctors (
  const size_t num_procs,
  const size_t threads_per_proc,
  const netuit::Topology& topology
) {

  const auto enumerated = netuit::GenerateMetisAssignments(
    num_procs, threads_per_proc, topology
  );

  return std::pair{
    enumerated.first,
    enumerated.second
  };

}

} // namespace netuit

#endif // #ifndef NETUIT_ASSIGN_GENERATEMETISASSIGNMENTS_HPP_INCLUDE