Program Listing for File hugenholtz.hpp

Return to documentation for file (SeQuant/core/hugenholtz.hpp)

//
// Created by Eduard Valeyev on 2019-02-14.
//

#ifndef SEQUANT_HUGENHOLTZ_HPP
#define SEQUANT_HUGENHOLTZ_HPP

namespace sequant {

template <typename Edge, typename EdgeEquality>
class HugenholtzVertex {
  using Group = std::pair<Edge, container::set<size_t>>;

 public:
  HugenholtzVertex() = default;
  HugenholtzVertex(const HugenholtzVertex&) = default;
  HugenholtzVertex(HugenholtzVertex&&) = default;

  template <typename EdgeRange, typename = std::enable_if_t<!std::is_same_v<
                                    HugenholtzVertex, std::decay_t<EdgeRange>>>>
  HugenholtzVertex(EdgeRange&& edge_range, EdgeEquality equals = EdgeEquality{})
      : equals_(std::move(equals)) {
    const auto num_edges = ranges::size(edge_range);
    edge_to_group_.reserve(num_edges);

    // compute the groups
    size_t edge_idx = 0;
    ranges::for_each(edge_range, [this, &edge_idx](const Edge& edge) {
      const auto grp_it = std::find_if(
          begin(groups_), end(groups_),
          [&edge, this](const Group& grp) { return equals_(grp.first, edge); });
      if (grp_it == end(groups_)) {  // no group yet? create
        groups_.emplace_back(
            std::make_pair(edge, typename Group::second_type{edge_idx}));
        edge_to_group_.push_back(groups_.size() - 1);
      } else {
        auto result = grp_it->second.insert(edge_idx);
        assert(result.second);
        edge_to_group_.push_back(grp_it - begin(groups_));
      }
      ++edge_idx;
    });
  }

  auto num_edges() const { return edge_to_group_.size(); }

  auto num_groups() const { return groups_.size(); }

  auto num_nonempty_groups() const {
    return std::accumulate(begin(groups_), end(groups_), size_t(0),
                           [](const size_t& left, const Group& right) {
                             return left + (right.second.empty() ? 0 : 1);
                           });
  }

  const auto& group(size_t edge_idx) const {
    const auto grp_idx = edge_to_group_.at(edge_idx);
    return groups_.at(grp_idx);
  }

  const auto& group_at(size_t grp_idx) const { return groups_.at(grp_idx); }

  const size_t group_size(size_t edge_idx) const {
    const auto result = group(edge_idx).second.size();
    assert(result > 0);
    return result;
  }

  void erase(size_t edge_idx, const Edge& edge) {
    // preconditions
    const auto grp_idx = edge_to_group_.at(edge_idx);
    Group& grp = groups_.at(grp_idx);
    assert(std::find_if(begin(groups_), end(groups_),
                        [this, &edge](const Group& grp) {
                          return equals_(grp.first, edge);
                        }) != end(groups_));
    assert(grp.second.find(edge_idx) != end(grp.second));
    assert(equals_(grp.first, edge));

    // remove the edge from the map and update the groups
    edge_to_group_.erase(edge_to_group_.begin() + edge_idx);
    grp.second.erase(edge_idx);
    for (auto&& g : groups_) {
      ranges::for_each(g.second, [&edge_idx](size_t& e) {
        if (e >= edge_idx) {
          assert(e > 0);
          --e;
        }
      });
    }
    // groups are never erased!
  }

  void insert(const size_t edge_idx, const Edge& edge) {
    // preconditions
    if (edge_idx > edge_to_group_.size()) {
      throw std::out_of_range(
          "HugenholtzVertex::insert : can only insert or append");
    }

    auto grp_it = std::find_if(
        begin(groups_), end(groups_),
        [this, &edge](const Group& grp) { return equals_(grp.first, edge); });

    // update existing edge indices if inserting in the middle
    if (edge_idx < edge_to_group_.size()) {
      for (auto&& g : groups_) {
        ranges::for_each(g.second, [&edge_idx](size_t& e) {
          if (e >= edge_idx) {
            ++e;
          }
        });
      }
    }

    // now update the group with the new index and update the edge->grp map
    if (grp_it == end(groups_)) {  // no group yet? create
      groups_.emplace_back(
          std::make_pair(edge, typename Group::second_type{edge_idx}));
      edge_to_group_.insert(edge_to_group_.begin() + edge_idx,
                            groups_.size() - 1);
    } else {  // just insert but keep the group indices sorted
      auto result = grp_it->second.insert(edge_idx);
      assert(result.second);
      edge_to_group_.insert(edge_to_group_.begin() + edge_idx,
                            grp_it - groups_.begin());
    }
  }

 private:
  container::svector<size_t> edge_to_group_;  // maps edge index to group idx
  container::svector<Group>
      groups_;  // group = Edge + (sorted) indices of the equivalent edges
  EdgeEquality equals_;
};

}  // namespace sequant

#endif  // SEQUANT_HUGENHOLTZ_HPP