Program Listing for File LocalCommunity.hpp

Return to documentation for file (include/networkit/structures/LocalCommunity.hpp)

#ifndef NETWORKIT_STRUCTURES_LOCAL_COMMUNITY_HPP_
#define NETWORKIT_STRUCTURES_LOCAL_COMMUNITY_HPP_

#include <set>
#include <unordered_map>
#include <unordered_set>

#include <networkit/graph/Graph.hpp>

namespace NetworKit {

template <bool ShellMaintainsExtDeg, bool MaintainBoundary = false, bool AllowRemoval = false>
class LocalCommunity {
    static_assert(!MaintainBoundary || ShellMaintainsExtDeg,
                  "boundary cannot be maintained without maintaining the external degree");

public:
    template <typename ValueType, bool used>
    struct OptionalValue {
        ValueType &get() { throw std::runtime_error("Getting value that is missing"); }

        const ValueType &get() const { throw std::runtime_error("Getting value that is missing"); }

        ValueType &operator*() { throw std::runtime_error("Getting value that is missing"); }

        const ValueType &operator*() const {
            throw std::runtime_error("Getting value that is missing");
        }

        ValueType *operator->() { throw std::runtime_error("Getting value that is missing"); }

        const ValueType *operator->() const {
            throw std::runtime_error("Getting value that is missing");
        }

        void set(ValueType) { throw std::runtime_error("Setting value that is missing"); }

        OptionalValue &operator+=(ValueType) {
            throw std::runtime_error("Increasing value that is missing");
        }

        OptionalValue &operator-=(ValueType) {
            throw std::runtime_error("Decreasing value that is missing");
        }

        OptionalValue(const ValueType &){};

        OptionalValue(){};
    };

    template <typename ValueType>
    struct OptionalValue<ValueType, true> {
        ValueType &get() { return value; }

        const ValueType &get() const { return value; }

        ValueType &operator*() { return value; }

        const ValueType &operator*() const { return value; }

        ValueType *operator->() { return &value; }

        const ValueType *operator->() const { return &value; }

        void set(ValueType v) { value = v; }

        template <typename SecondValue,
                  typename std::enable_if<std::is_scalar<SecondValue>::value, int>::type = 0>
        OptionalValue &operator+=(SecondValue v) {
            value += v;
            return *this;
        }

        template <typename SecondValue,
                  typename std::enable_if<std::is_scalar<SecondValue>::value, int>::type = 0>
        OptionalValue &operator-=(SecondValue v) {
            value -= v;
            return *this;
        }

        OptionalValue(const ValueType &v) : value(v){};

        OptionalValue() : value(){};

        ValueType value;
    };

    struct ShellInfo {
        OptionalValue<double, true> intDeg;

        OptionalValue<double, ShellMaintainsExtDeg> extDeg;

        OptionalValue<count, MaintainBoundary> numExclusiveBoundaryMembers;

        int64_t boundaryChange() const {
            int64_t boundary_diff = -1 * numExclusiveBoundaryMembers.get();

            if (extDeg.get() > 0) {
                boundary_diff += 1;
            }

            return boundary_diff;
        }

        ShellInfo() : intDeg(0), extDeg(0), numExclusiveBoundaryMembers(0) {}
    };

    struct CommunityInfo {
        OptionalValue<double, AllowRemoval> intDeg;

        OptionalValue<double, ShellMaintainsExtDeg && AllowRemoval> extDeg;

        OptionalValue<node, MaintainBoundary && AllowRemoval> exclusiveOutsideNeighbor;

        OptionalValue<count, MaintainBoundary && AllowRemoval> numFullyInternalNeighbors;

        int64_t boundaryChange() const {
            int64_t boundary_diff = numFullyInternalNeighbors.get();

            if (extDeg.get() > 0) {
                boundary_diff -= 1;
            }

            return boundary_diff;
        }

        CommunityInfo()
            : intDeg(0), extDeg(0), exclusiveOutsideNeighbor(none), numFullyInternalNeighbors(0) {}
    };

    LocalCommunity(const Graph &G);

    void addNode(node u);

    void removeNode(node u);

    bool contains(node u) const;

    std::set<node> toSet() const;

    template <typename F>
    void forShellNodes(F callback) {
        for (const auto &it : shell) {

#ifdef NETWORKIT_SANITY_CHECKS
#ifndef NDEBUG
            auto intExtDeg = calculateIntExtDeg(it.first);
            assert(*it.second.intDeg == intExtDeg.first);
            if (ShellMaintainsExtDeg) {
                assert(*it.second.extDeg == intExtDeg.second);
            }

            if (MaintainBoundary) {
                int64_t boundary_diff_debug = 0;
                bool v_in_boundary = false;
                G->forNeighborsOf(it.first, [&](node x) {
                    auto it = currentBoundary->find(x);
                    if (it != currentBoundary->end()) {
                        if (it->second == 1) {
                            boundary_diff_debug -= 1;
                        }
                    } else if (!v_in_boundary) {
                        boundary_diff_debug += 1;
                        v_in_boundary = true;
                    }
                });

                assert(it.second.boundaryChange() == boundary_diff_debug);
            }
#endif // NDEBUG
#endif // NETWORKIT_SANITY_CHECKS

            callback(it.first, it.second);
        }
    }

    template <typename F>
    void forCommunityNodes(F callback) {
        for (auto it = community.cbegin(); it != community.cend();) {
            // advance the iterator so the current element may be deleted
            const auto cit = it++;
#ifdef NETWORKIT_SANITY_CHECKS
#ifndef NDEBUG
            if (AllowRemoval) {
                auto intExtDeg = calculateIntExtDeg(cit->first);
                assert(*cit->second.intDeg == intExtDeg.first);
                if (ShellMaintainsExtDeg) {
                    assert(*cit->second.extDeg == intExtDeg.second);
                }

                if (MaintainBoundary) {
                    int64_t boundary_diff_debug = 0;
                    bool v_in_boundary = false;
                    G->forNeighborsOf(cit->first, [&](node x) {
                        if (community.find(x) == community.end()) {
                            if (!v_in_boundary) {
                                boundary_diff_debug -= 1;
                                v_in_boundary = true;
                            }
                        } else if (currentBoundary->find(x) == currentBoundary->end()) {
                            boundary_diff_debug += 1;
                        }
                    });

                    assert(cit->second.boundaryChange() == boundary_diff_debug);
                }
            }
#endif // NDEBUG
#endif // NETWORKIT_SANITY_CHECKS

            callback(cit->first, cit->second);
        }
    }

    size_t size() const { return community.size(); }

    double internalEdgeWeight() const { return intWeight; }

    double cut() const { return extWeight; }

    count boundarySize() const { return currentBoundary->size(); }

private:
    const Graph *G;
    std::unordered_map<node, CommunityInfo> community;
    std::unordered_map<node, ShellInfo> shell;
    double intWeight;
    double extWeight;
    OptionalValue<std::unordered_map<node, count>, MaintainBoundary> currentBoundary;

    // The boundary is defined as all nodes of C that have a neighbor not in C
    std::unordered_set<node> calculateBoundary();

    std::pair<double, double> calculateIntExtDeg(node v);

    std::pair<double, double> calculateVolumeCut();
};

} // namespace NetworKit

#endif // NETWORKIT_STRUCTURES_LOCAL_COMMUNITY_HPP_