Program Listing for File Topology.hpp

Return to documentation for file (include/netuit/topology/Topology.hpp)

#pragma once
#ifndef NETUIT_TOPOLOGY_TOPOLOGY_HPP_INCLUDE
#define NETUIT_TOPOLOGY_TOPOLOGY_HPP_INCLUDE

#include <algorithm>
#include <exception>
#include <iostream>
#include <iterator>
#include <set>
#include <sstream>
#include <string>
#include <unordered_map>
#include <utility>

#include <metis.h>

#include "../../../third-party/Empirical/source/tools/hash_utils.h"
#include "../../../third-party/Empirical/source/tools/keyname_utils.h"

#include "../../uitsl/debug/EnumeratedFunctor.hpp"
#include "../../uitsl/debug/metis_utils.hpp"
#include "../../uitsl/polyfill/identity.hpp"
#include "../../uitsl/utility/stream_utils.hpp"

#include "TopoNode.hpp"

namespace netuit {

class Topology {
public:
  using node_id_t = size_t;
  using edge_id_t = size_t;
private:
  using topology_t = emp::vector<TopoNode>;
  topology_t topology;

  // unordered_maps of edge ids to node ids
  std::unordered_map<edge_id_t, node_id_t> input_registry;
  std::unordered_map<edge_id_t, node_id_t> output_registry;

  // map of index to node_id
  std::function<node_id_t(node_id_t)> index_map{std::identity};

  void RegisterNode(const node_id_t node_id, const netuit::TopoNode& topo_node) {
    RegisterNodeInputs(node_id, topo_node);
    RegisterNodeOutputs(node_id, topo_node);
  }

  void RegisterNodeInputs(const node_id_t node_id, const netuit::TopoNode& topo_node) {
    for (const auto& input : topo_node.GetInputs()) {
      emp_assert(input_registry.count(input.GetEdgeID()) == 0);
      input_registry[input.GetEdgeID()] = node_id;
    }
  }

  void RegisterNodeOutputs(const node_id_t node_id, const netuit::TopoNode& topo_node) {
    for (const auto& output : topo_node.GetOutputs()) {
      emp_assert(output_registry.count(output.GetEdgeID()) == 0);
      output_registry[output.GetEdgeID()] = node_id;
    }
  }

  emp::vector<node_id_t> GetNodeOutputs(const netuit::TopoNode& node) const {
    emp::vector<node_id_t> res;
    for (const auto& edge : node.GetOutputs()) {
      res.push_back(input_registry.at(edge.GetEdgeID()));
    }
    return res;
  }

public:
  using value_type = TopoNode;

  Topology() = default;

  template <typename... Args>
  Topology(Args&&... args) : topology(std::forward<Args>(args)...) {
    for (size_t i = 0; i < topology.size(); ++i) {
      RegisterNode(i, topology[i]);
    }
  }

  Topology(std::istream& is) {
    emp::vector<std::string> lines;
    // read file lines into vector
    uitsl::read_lines(is, std::back_inserter(lines));

    // map of node ids to nodes
    std::map<node_id_t, TopoNode> node_map;
    // put nodes into map
    for (const std::string& line : lines) {
      std::istringstream iss(line);
      node_id_t node_id;
      iss >> node_id;
      iss >> node_map[node_id];
    }
    // make sure we inserted every line
    emp_assert(lines.size() == node_map.size());
    // make sure the node ids are less than the number of lines
    emp_assert( std::all_of(
      std::begin(node_map),
      std::end(node_map),
      [num_lines = lines.size()](const auto& kv) {
        const auto& [id, node] = kv;
        return id < num_lines;
      }
    ) );

    // insert all nodes into topology
    std::for_each(
      std::begin(node_map),
      std::end(node_map),
      [this](const auto& kv) {
        const auto& [id, node] = kv;
        push_back(node);
      }
    );
  }
  // topology iterators must only be const
  topology_t::const_iterator begin() const noexcept { return topology.begin(); }
  topology_t::const_iterator end() const noexcept { return topology.end(); }

  topology_t::const_iterator cbegin() const noexcept { return topology.cbegin(); }
  topology_t::const_iterator cend() const noexcept { return topology.cend(); }

  void push_back(const TopoNode& node) {
    const size_t id = topology.size();
    topology.push_back(node);
    RegisterNode(id, node);
  }

  void push_back(TopoNode&& node) {
    const size_t id = topology.size();
    RegisterNode(id, node);
    topology.push_back(std::move(node));
  }

  template <typename... Args>
  void emplace_back(Args&&... args) {
    const size_t id = topology.size();
    topology.emplace_back(std::forward(args)...);
    RegisterNode(id, topology[id]);
  }
  size_t GetSize() const noexcept { return topology.size(); }

  const TopoNode& operator[](size_t n) const { return topology[n]; }

  void SetMap(const std::unordered_map<node_id_t, node_id_t>& map) {
    index_map = uitsl::EnumeratedFunctor<node_id_t, node_id_t>(map);
  }

  node_id_t GetCanonicalNodeID(const node_id_t node_id) const {
    return index_map(node_id);
  }

  auto AsCSR() const {
    // get vector with degree of each node
    emp::vector<int> degrees;
    std::transform(
      std::begin(topology),
      std::end(topology),
      std::back_inserter(degrees),
      [](const auto& node){ return node.GetNumOutputs(); }
    );
    // get each starting position of each node's adjacency list
    emp::vector<int> x_adj{0};
    std::partial_sum(
      std::begin(degrees),
      std::end(degrees),
      std::back_inserter(x_adj)
    );
    // build vector of concatenated adjacency lists
    emp::vector<int> adjacency;
    std::for_each(
      std::begin(topology),
      std::end(topology),
      [this, &adjacency](const auto& node){
        const auto outputs = GetNodeOutputs(node);
        adjacency.insert(
          std::end(adjacency),
          std::begin(outputs),
          std::end(outputs)
        );
      }
    );  return std::make_pair(x_adj, adjacency);
  }

  emp::vector<idx_t> Optimize(int32_t parts) const {
    // set up variables
    idx_t nodes = topology.size();
    idx_t n_cons = 1;
    idx_t volume;

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

    // set up result vector
    emp::vector<idx_t> result(nodes);

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

    // call partitioning algorithm
    int status = METIS_PartGraphKway(
      &nodes, // number of vertices in the graph
      &n_cons, // number of balancing constraints.
      xadj.data(), // array of node indexes into adjacency[]
      adjacency.data(), // array of adjacenct nodes for every node
      nullptr, // weights of nodes
      nullptr, // size of nodes for total comunication value
      nullptr, // weights of edges
      &parts, // number of parts to partition the graph into
      nullptr, // weight for each partition and constraint
      nullptr, // allowed load imbalance tolerance for each constraint
      nullptr, // array of options
      &volume, // edge-cut or total comm volume of the solution
      result.data() // partition vector of the graph
    );

    uitsl::metis::verify(status);

    return result;
  }

  void PrintEdgeList(std::ostream& os = std::cout) const noexcept {
    for (size_t i = 0; i < topology.size(); ++i) {
      for (const auto& output : topology[i].GetOutputs()) {
        os << i << " " << output << std::endl;
      }
    }
  }

  void PrintAdjacencyList(std::ostream& os = std::cout) const noexcept {
    for (size_t i = 0; i < topology.size(); ++i) {
      os << i;
      for (const auto& output : GetNodeOutputs(topology[i])) {
        os << " " << output;
      }
      os << std::endl;
    }
  }

  void PrintPartition(const emp::vector<int>& partition, std::ostream& os = std::cout) const {
    auto name_node = [&partition] (const size_t index) -> std::string {
      return emp::keyname::pack({
        {"node_id", emp::to_string(index)},
        {"partition", emp::to_string(partition[index])}
      });
    };

    for (size_t i = 0; i < topology.size(); ++i) {
      os << name_node(i) << " ";
      os << topology[i].GetNumOutputs() << std::endl;

      for (const auto& node : GetNodeOutputs(topology[i])) {
        os << name_node(node) << std::endl;
      }
    }
  }

  Topology GetSubTopology(const std::unordered_set<size_t>& node_ids) const {
    emp::vector<netuit::TopoNode> nodes;
    std::unordered_map<node_id_t, node_id_t> translator;

    // fill subtopology with all nodes in node_ids
    for (const size_t i : node_ids) {
      nodes.push_back(topology[i]);
      translator[translator.size()] = i;
    }

    // fix subtopology to exclude external nodes
    for (auto& node : nodes) {
      for (const auto& output : node.GetOutputs()) {
        if (!node_ids.count(
          output_registry.at(output.GetEdgeID())
        )) node.RemoveOutput(output);
      }
      for (const auto& input : node.GetInputs()) {
        if (node_ids.count(
          input_registry.at(input.GetEdgeID())
        )) node.RemoveInput(input);
      }
    }
    Topology subtopo(nodes);
    subtopo.SetMap(translator);
    return subtopo;
  }

  std::string ToString() const noexcept {
    std::ostringstream oss;
    PrintAdjacencyList(oss);
    return oss.str();
  }
};

} // namespace netuit

#endif // #ifndef NETUIT_TOPOLOGY_TOPOLOGY_HPP_INCLUDE