↰ Return to documentation for file (include/networkit/graph/Graph.hpp
)
/*
* Graph.hpp
*
* Created on: 01.06.2014
* Author: Christian Staudt
* Klara Reichard <klara.reichard@gmail.com>
* Marvin Ritter <marvin.ritter@gmail.com>
*/
#ifndef NETWORKIT_GRAPH_GRAPH_HPP_
#define NETWORKIT_GRAPH_GRAPH_HPP_
#include <algorithm>
#include <fstream>
#include <functional>
#include <iostream>
#include <memory>
#include <numeric>
#include <omp.h>
#include <queue>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <typeindex>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#include <networkit/Globals.hpp>
#include <networkit/auxiliary/ArrayTools.hpp>
#include <networkit/auxiliary/FunctionTraits.hpp>
#include <networkit/auxiliary/Log.hpp>
#include <networkit/auxiliary/Random.hpp>
#include <tlx/define/deprecated.hpp>
namespace NetworKit {
struct Edge {
node u, v;
Edge() : u(none), v(none) {}
Edge(node _u, node _v, bool sorted = false) {
u = sorted ? std::min(_u, _v) : _u;
v = sorted ? std::max(_u, _v) : _v;
}
};
struct WeightedEdge : Edge {
edgeweight weight;
// Needed by cython
WeightedEdge() : Edge(), weight(std::numeric_limits<edgeweight>::max()) {}
WeightedEdge(node u, node v, edgeweight w) : Edge(u, v), weight(w) {}
};
struct WeightedEdgeWithId : WeightedEdge {
edgeid eid;
WeightedEdgeWithId(node u, node v, edgeweight w, edgeid eid)
: WeightedEdge(u, v, w), eid(eid) {}
};
inline bool operator==(const Edge &e1, const Edge &e2) {
return e1.u == e2.u && e1.v == e2.v;
}
inline bool operator<(const WeightedEdge &e1, const WeightedEdge &e2) {
return e1.weight < e2.weight;
}
struct Unsafe {};
static constexpr Unsafe unsafe{};
} // namespace NetworKit
namespace std {
template <>
struct hash<NetworKit::Edge> {
size_t operator()(const NetworKit::Edge &e) const { return hash_node(e.u) ^ hash_node(e.v); }
hash<NetworKit::node> hash_node;
};
} // namespace std
namespace NetworKit {
// forward declaration to randomization/CurveballImpl.hpp
namespace CurveballDetails {
class CurveballMaterialization;
}
class Graph final {
// graph attributes
count n;
count m;
count storedNumberOfSelfLoops;
node z;
edgeid omega;
count t;
bool weighted;
bool directed;
bool edgesIndexed;
bool maintainCompactEdges = false;
bool maintainSortedEdges = false;
edgeid deletedID;
// per node data
std::vector<bool> exists;
std::vector<std::vector<node>> inEdges;
std::vector<std::vector<node>> outEdges;
std::vector<std::vector<edgeweight>> inEdgeWeights;
std::vector<std::vector<edgeweight>> outEdgeWeights;
std::vector<std::vector<edgeid>> inEdgeIds;
std::vector<std::vector<edgeid>> outEdgeIds;
private:
// base class for all node (and edge) attribute
// storages with attribute type info
// independent of the attribute type, holds bookkeeping info only:
// - attribute name
// - type info of derived (real storage holding) classes
// - which indices are valid
// - number of valid indices
// - the associated graph (who knows, which nodes/edges exist)
// - the validity of the whole storage (initially true, false after detach)
// all indexed accesses by NetworKit::index: synonym both for node and edgeid
class PerNode {
public:
static constexpr bool edges = false;
};
class PerEdge {
public:
static constexpr bool edges = true;
};
template <typename NodeOrEdge>
class AttributeStorageBase { // alias ASB
public:
AttributeStorageBase(const Graph *graph, std::string name, std::type_index type)
: name{std::move(name)}, type{type}, theGraph{graph}, validStorage{true} {
checkPremise(); // node for PerNode, theGraph.hasEdgeIds() for PerEdges
}
void invalidateStorage() { validStorage = false; }
const std::string &getName() const noexcept { return name; }
std::type_index getType() const noexcept { return type; }
bool isValid(index n) const noexcept { return n < valid.size() && valid[n]; }
// Called by Graph when node/edgeid n is deleted.
void invalidate(index n) {
if (isValid(n)) {
valid[n] = false;
--validElements;
}
}
protected:
void markValid(index n) {
indexOK(n); // specialized for node/edgeid
if (n >= valid.size())
valid.resize(n + 1);
if (!valid[n]) {
valid[n] = true;
++validElements;
}
}
void checkIndex(index n) const {
indexOK(n);
if (!isValid(n)) {
throw std::runtime_error("Invalid attribute value");
}
}
private:
std::string name;
std::type_index type;
std::vector<bool> valid; // For each node/edgeid: whether attribute is set or not.
protected:
void indexOK(index n) const;
void checkPremise() const;
index validElements = 0;
const Graph *theGraph;
bool validStorage; // Validity of the whole storage
}; // class AttributeStorageBase
template <typename NodeOrEdge>
using ASB = AttributeStorageBase<NodeOrEdge>;
template <typename NodeOrEdge, typename T, bool isConst>
class Attribute;
template <typename NodeOrEdge, template <typename> class Base, typename T>
class AttributeStorage : public Base<NodeOrEdge> {
public:
AttributeStorage(const Graph *theGraph, std::string name)
: Base<NodeOrEdge>{theGraph, std::move(name), typeid(T)} {}
void resize(index i) {
if (i >= values.size())
values.resize(i + 1);
}
auto size() const noexcept { return this->validElements; }
void set(index i, T &&v) {
this->markValid(i);
resize(i);
values[i] = std::move(v);
}
// instead of returning an std::optional (C++17) we provide these
// C++14 options
// (1) throw an exception when invalid:
T get(index i) const { // may throw
this->checkIndex(i);
return values[i];
}
// (2) give default value when invalid:
T get(index i, T defaultT) const noexcept {
if (i >= values.size() || !this->isValid(i))
return defaultT;
return values[i];
}
friend Attribute<NodeOrEdge, T, true>;
friend Attribute<NodeOrEdge, T, false>;
private:
using Base<NodeOrEdge>::theGraph;
std::vector<T> values; // the real attribute storage
}; // class AttributeStorage<NodeOrEdge, Base, T>
template <typename NodeOrEdge, typename T, bool isConst>
class Attribute {
public:
using AttributeStorage_type =
std::conditional_t<isConst, const AttributeStorage<NodeOrEdge, ASB, T>,
AttributeStorage<NodeOrEdge, ASB, T>>;
class Iterator {
public:
// The value type of the attribute. Returned by
// operator*().
using value_type = T;
// Reference to the value_type, required by STL.
using reference = std::conditional_t<isConst, const value_type &, value_type &>;
// Pointer to the value_type, required by STL.
using pointer = std::conditional_t<isConst, const value_type *, value_type *>;
// STL iterator category.
using iterator_category = std::forward_iterator_tag;
// Signed integer type of the result of subtracting two pointers,
// required by STL.
using difference_type = ptrdiff_t;
Iterator() : storage{nullptr}, idx{0} {}
Iterator(AttributeStorage_type *storage) : storage{storage}, idx{0} {
if (storage) {
nextValid();
}
}
Iterator &nextValid() {
while (storage && !storage->isValid(idx)) {
if (idx >= storage->values.size()) {
storage = nullptr;
return *this;
}
++idx;
}
return *this;
}
Iterator &operator++() {
if (!storage) {
throw std::runtime_error("Invalid attribute iterator");
}
++idx;
return nextValid();
}
auto operator*() const {
if (!storage) {
throw std::runtime_error("Invalid attribute iterator");
}
return std::make_pair(idx, storage->values[idx]);
}
bool operator==(Iterator const &iter) const noexcept {
if (storage == nullptr && iter.storage == nullptr) {
return true;
}
return storage == iter.storage && idx == iter.idx;
}
bool operator!=(Iterator const &iter) const noexcept { return !(*this == iter); }
private:
AttributeStorage_type *storage;
index idx;
}; // class Iterator
private:
class IndexProxy {
// a helper class for distinguished read and write on an indexed
// attribute
// operator[] on an attribute yields an IndexProxy holding
// location and index of access
// - casting an IndexProxy to the attribute type reads the value
// - assigning to it (operator=) writes the value
public:
IndexProxy(AttributeStorage_type *storage, index idx) : storage{storage}, idx{idx} {}
// reading at idx
operator T() const {
storage->checkIndex(idx);
return storage->values[idx];
}
// writing at idx
template <bool ic = isConst>
std::enable_if_t<!ic, T> &operator=(T &&other) {
storage->set(idx, std::move(other));
return storage->values[idx];
}
private:
AttributeStorage_type *storage;
index idx;
}; // class IndexProxy
public:
explicit Attribute(std::shared_ptr<AttributeStorage_type> ownedStorage = nullptr)
: ownedStorage{ownedStorage}, valid{ownedStorage != nullptr} {}
Attribute(Attribute const &other) : ownedStorage{other.ownedStorage}, valid{other.valid} {}
template <bool ic = isConst, std::enable_if_t<ic, int> = 0>
Attribute(Attribute<NodeOrEdge, T, false> const &other)
: ownedStorage{other.ownedStorage}, valid{other.valid} {}
Attribute &operator=(Attribute other) {
this->swap(other);
return *this;
}
void swap(Attribute &other) {
std::swap(ownedStorage, other.ownedStorage);
std::swap(valid, other.valid);
}
Attribute(Attribute &&other) noexcept
: ownedStorage{std::move(other.ownedStorage)}, valid{other.valid} {
other.valid = false;
}
template <bool ic = isConst, std::enable_if_t<ic, int> = 0>
Attribute(Attribute<NodeOrEdge, T, false> &&other) noexcept
: ownedStorage{std::move(other.ownedStorage)}, valid{other.valid} {
other.valid = false;
}
auto begin() const {
checkAttribute();
return Iterator(ownedStorage.get()).nextValid();
}
auto end() const { return Iterator(nullptr); }
auto size() const noexcept { return ownedStorage->size(); }
template <bool ic = isConst>
std::enable_if_t<!ic> set(index i, T v) {
checkAttribute();
ownedStorage->set(i, std::move(v));
}
template <bool ic = isConst>
std::enable_if_t<!ic> set2(node u, node v, T t) {
static_assert(NodeOrEdge::edges, "attribute(u,v) for edges only");
set(ownedStorage->theGraph->edgeId(u, v), t);
}
auto get(index i) const {
checkAttribute();
return ownedStorage->get(i);
}
auto get2(node u, node v) const {
static_assert(NodeOrEdge::edges, "attribute(u,v) for edges only");
return get(ownedStorage->theGraph->edgeId(u, v));
}
auto get(index i, T defaultT) const {
checkAttribute();
return ownedStorage->get(i, defaultT);
}
auto get2(node u, node v, T defaultT) const {
static_assert(NodeOrEdge::edges, "attribute(u,v) for edges only");
return get(ownedStorage->theGraph->edgeId(u, v), defaultT);
}
IndexProxy operator[](index i) const {
checkAttribute();
return IndexProxy(ownedStorage.get(), i);
}
IndexProxy operator()(node u, node v) const {
static_assert(NodeOrEdge::edges, "attribute(u,v) for edges only");
checkAttribute();
return IndexProxy(ownedStorage.get(), ownedStorage->theGraph->edgeId(u, v));
}
void checkAttribute() const {
if (!ownedStorage->validStorage)
throw std::runtime_error("Invalid attribute");
}
auto getName() const {
checkAttribute();
return ownedStorage->getName();
}
void write(std::string const &filename) const {
std::ofstream out(filename);
if (!out)
ERROR("cannot open ", filename, " for writing");
for (auto it = begin(); it != end(); ++it) {
auto pair = *it;
auto n = pair.first; // node/edgeid
auto v = pair.second; // value
out << n << "\t" << v << "\n";
}
out.close();
}
template <bool ic = isConst>
std::enable_if_t<!ic> read(const std::string &filename) {
std::ifstream in(filename);
if (!in) {
ERROR("cannot open ", filename, " for reading");
}
index n; // node/edgeid
T v; // value
std::string line;
while (std::getline(in, line)) {
std::istringstream istring(line);
istring >> n >> v;
set(n, v);
}
}
private:
std::shared_ptr<AttributeStorage_type> ownedStorage;
bool valid;
}; // class Attribute
template <typename NodeOrEdge>
class AttributeMap {
friend Graph;
const Graph *theGraph;
public:
std::unordered_map<std::string, std::shared_ptr<ASB<NodeOrEdge>>> attrMap;
AttributeMap(const Graph *g) : theGraph{g} {}
auto find(std::string const &name) {
auto it = attrMap.find(name);
if (it == attrMap.end()) {
throw std::runtime_error("No such attribute");
}
return it;
}
auto find(std::string const &name) const {
auto it = attrMap.find(name);
if (it == attrMap.end()) {
throw std::runtime_error("No such attribute");
}
return it;
}
template <typename T>
auto attach(const std::string &name) {
auto ownedPtr =
std::make_shared<AttributeStorage<NodeOrEdge, ASB, T>>(theGraph, std::string{name});
auto insertResult = attrMap.emplace(ownedPtr->getName(), ownedPtr);
auto success = insertResult.second;
if (!success) {
throw std::runtime_error("Attribute with same name already exists");
}
return Attribute<NodeOrEdge, T, false>{ownedPtr};
}
void detach(const std::string &name) {
auto it = find(name);
auto storage = it->second.get();
storage->invalidateStorage();
it->second.reset();
attrMap.erase(name);
}
template <typename T>
auto get(const std::string &name) {
auto it = find(name);
if (it->second.get()->getType() != typeid(T))
throw std::runtime_error("Type mismatch in Attributes().get()");
return Attribute<NodeOrEdge, T, false>{
std::static_pointer_cast<AttributeStorage<NodeOrEdge, ASB, T>>(it->second)};
}
template <typename T>
auto get(const std::string &name) const {
auto it = find(name);
if (it->second.get()->getType() != typeid(T))
throw std::runtime_error("Type mismatch in Attributes().get()");
return Attribute<NodeOrEdge, T, true>{
std::static_pointer_cast<const AttributeStorage<NodeOrEdge, ASB, T>>(it->second)};
}
}; // class AttributeMap
AttributeMap<PerNode> nodeAttributeMap;
AttributeMap<PerEdge> edgeAttributeMap;
public:
auto &nodeAttributes() noexcept { return nodeAttributeMap; }
const auto &nodeAttributes() const noexcept { return nodeAttributeMap; }
auto &edgeAttributes() noexcept { return edgeAttributeMap; }
const auto &edgeAttributes() const noexcept { return edgeAttributeMap; }
// wrap up some typed attributes for the cython interface:
//
auto attachNodeIntAttribute(const std::string &name) {
nodeAttributes().theGraph = this;
return nodeAttributes().attach<int>(name);
}
auto attachEdgeIntAttribute(const std::string &name) {
edgeAttributes().theGraph = this;
return edgeAttributes().attach<int>(name);
}
auto attachNodeDoubleAttribute(const std::string &name) {
nodeAttributes().theGraph = this;
return nodeAttributes().attach<double>(name);
}
auto attachEdgeDoubleAttribute(const std::string &name) {
edgeAttributes().theGraph = this;
return edgeAttributes().attach<double>(name);
}
auto attachNodeStringAttribute(const std::string &name) {
nodeAttributes().theGraph = this;
return nodeAttributes().attach<std::string>(name);
}
auto attachEdgeStringAttribute(const std::string &name) {
edgeAttributes().theGraph = this;
return edgeAttributes().attach<std::string>(name);
}
auto getNodeIntAttribute(const std::string &name) {
nodeAttributes().theGraph = this;
return nodeAttributes().get<int>(name);
}
auto getEdgeIntAttribute(const std::string &name) {
edgeAttributes().theGraph = this;
return edgeAttributes().get<int>(name);
}
auto getNodeDoubleAttribute(const std::string &name) {
nodeAttributes().theGraph = this;
return nodeAttributes().get<double>(name);
}
auto getEdgeDoubleAttribute(const std::string &name) {
edgeAttributes().theGraph = this;
return edgeAttributes().get<double>(name);
}
auto getNodeStringAttribute(const std::string &name) {
nodeAttributes().theGraph = this;
return nodeAttributes().get<std::string>(name);
}
auto getEdgeStringAttribute(const std::string &name) {
edgeAttributes().theGraph = this;
return edgeAttributes().get<std::string>(name);
}
void detachNodeAttribute(std::string const &name) {
nodeAttributes().theGraph = this;
nodeAttributes().detach(name);
}
void detachEdgeAttribute(std::string const &name) {
edgeAttributes().theGraph = this;
edgeAttributes().detach(name);
}
using NodeIntAttribute = Attribute<PerNode, int, false>;
using NodeDoubleAttribute = Attribute<PerNode, double, false>;
using NodeStringAttribute = Attribute<PerNode, std::string, false>;
using EdgeIntAttribute = Attribute<PerEdge, int, false>;
using EdgeDoubleAttribute = Attribute<PerEdge, double, false>;
using EdgeStringAttribute = Attribute<PerEdge, std::string, false>;
private:
index indexInInEdgeArray(node v, node u) const;
index indexInOutEdgeArray(node u, node v) const;
edgeweight computeWeightedDegree(node u, bool inDegree = false,
bool countSelfLoopsTwice = false) const;
template <bool hasWeights>
inline edgeweight getOutEdgeWeight(node u, index i) const;
template <bool hasWeights>
inline edgeweight getInEdgeWeight(node u, index i) const;
template <bool graphHasEdgeIds>
inline edgeid getOutEdgeId(node u, index i) const;
template <bool graphHasEdgeIds>
inline edgeid getInEdgeId(node u, index i) const;
template <bool graphIsDirected>
inline bool useEdgeInIteration(node u, node v) const;
template <bool graphIsDirected, bool hasWeights, bool graphHasEdgeIds, typename L>
inline void forOutEdgesOfImpl(node u, L handle) const;
template <bool graphIsDirected, bool hasWeights, bool graphHasEdgeIds, typename L>
inline void forInEdgesOfImpl(node u, L handle) const;
template <bool graphIsDirected, bool hasWeights, bool graphHasEdgeIds, typename L>
inline void forEdgeImpl(L handle) const;
template <bool graphIsDirected, bool hasWeights, bool graphHasEdgeIds, typename L>
inline void parallelForEdgesImpl(L handle) const;
template <bool graphIsDirected, bool hasWeights, bool graphHasEdgeIds, typename L>
inline double parallelSumForEdgesImpl(L handle) const;
/*
* In the following definition, Aux::FunctionTraits is used in order to only
* execute lambda functions with the appropriate parameters. The
* decltype-return type is used for determining the return type of the
* lambda (needed for summation) but also determines if the lambda accepts
* the correct number of parameters. Otherwise the return type declaration
* fails and the function is excluded from overload resolution. Then there
* are multiple possible lambdas with three (third parameter id or weight)
* and two (second parameter can be second node id or edge weight for
* neighbor iterators). This is checked using Aux::FunctionTraits and
* std::enable_if. std::enable_if only defines the type member when the
* given bool is true, this bool comes from std::is_same which compares two
* types. The function traits give either the parameter type or if it is out
* of bounds they define type as void.
*/
template <class F, void * = (void *)0>
typename Aux::FunctionTraits<F>::result_type edgeLambda(F &, ...) const {
// the strange condition is used in order to delay the evaluation of the
// static assert to the moment when this function is actually used
static_assert(!std::is_same<F, F>::value,
"Your lambda does not support the required parameters or the "
"parameters have the wrong type.");
return std::declval<typename Aux::FunctionTraits<F>::result_type>(); // use the correct
// return type (this
// won't compile)
}
template <class F,
typename std::enable_if<
(Aux::FunctionTraits<F>::arity >= 3)
&& std::is_same<edgeweight,
typename Aux::FunctionTraits<F>::template arg<2>::type>::value
&& std::is_same<edgeid, typename Aux::FunctionTraits<F>::template arg<3>::type>::
value>::type * = (void *)0>
auto edgeLambda(F &f, node u, node v, edgeweight ew,
edgeid id) const -> decltype(f(u, v, ew, id)) {
return f(u, v, ew, id);
}
template <
class F,
typename std::enable_if<
(Aux::FunctionTraits<F>::arity >= 2)
&& std::is_same<edgeid, typename Aux::FunctionTraits<F>::template arg<2>::type>::value
&& std::is_same<node, typename Aux::FunctionTraits<F>::template arg<1>::type>::
value /* prevent f(v, weight, eid)
*/
>::type * = (void *)0>
auto edgeLambda(F &f, node u, node v, edgeweight, edgeid id) const -> decltype(f(u, v, id)) {
return f(u, v, id);
}
template <class F,
typename std::enable_if<
(Aux::FunctionTraits<F>::arity >= 2)
&& std::is_same<edgeweight, typename Aux::FunctionTraits<F>::template arg<
2>::type>::value>::type * = (void *)0>
auto edgeLambda(F &f, node u, node v, edgeweight ew,
edgeid /*id*/) const -> decltype(f(u, v, ew)) {
return f(u, v, ew);
}
template <class F, typename std::enable_if<
(Aux::FunctionTraits<F>::arity >= 1)
&& std::is_same<node, typename Aux::FunctionTraits<F>::template arg<
1>::type>::value>::type * = (void *)0>
auto edgeLambda(F &f, node u, node v, edgeweight /*ew*/,
edgeid /*id*/) const -> decltype(f(u, v)) {
return f(u, v);
}
template <class F,
typename std::enable_if<
(Aux::FunctionTraits<F>::arity >= 1)
&& std::is_same<edgeweight, typename Aux::FunctionTraits<F>::template arg<
1>::type>::value>::type * = (void *)0>
auto edgeLambda(F &f, node, node v, edgeweight ew, edgeid /*id*/) const -> decltype(f(v, ew)) {
return f(v, ew);
}
template <class F, void * = (void *)0>
auto edgeLambda(F &f, node, node v, edgeweight, edgeid) const -> decltype(f(v)) {
return f(v);
}
template <class F>
auto callBFSHandle(F &f, node u, count dist) const -> decltype(f(u, dist)) {
return f(u, dist);
}
template <class F>
auto callBFSHandle(F &f, node u, count) const -> decltype(f(u)) {
return f(u);
}
public:
class NodeIterator {
const Graph *G;
node u;
public:
// The value type of the nodes (i.e. nodes). Returned by
// operator*().
using value_type = node;
// Reference to the value_type, required by STL.
using reference = value_type &;
// Pointer to the value_type, required by STL.
using pointer = value_type *;
// STL iterator category.
using iterator_category = std::forward_iterator_tag;
// Signed integer type of the result of subtracting two pointers,
// required by STL.
using difference_type = ptrdiff_t;
// Own type.
using self = NodeIterator;
NodeIterator(const Graph *G, node u) : G(G), u(u) {
if (!G->hasNode(u) && u < G->upperNodeIdBound()) {
++(*this);
}
}
NodeIterator() : G(nullptr) {}
~NodeIterator() = default;
NodeIterator &operator++() {
assert(u < G->upperNodeIdBound());
do {
++u;
} while (!(G->hasNode(u) || u >= G->upperNodeIdBound()));
return *this;
}
NodeIterator operator++(int) {
const auto tmp = *this;
++(*this);
return tmp;
}
NodeIterator operator--() {
assert(u);
do {
--u;
} while (!G->hasNode(u));
return *this;
}
NodeIterator operator--(int) {
const auto tmp = *this;
--(*this);
return tmp;
}
bool operator==(const NodeIterator &rhs) const noexcept { return u == rhs.u; }
bool operator!=(const NodeIterator &rhs) const noexcept { return !(*this == rhs); }
node operator*() const noexcept {
assert(u < G->upperNodeIdBound());
return u;
}
};
class NodeRange {
const Graph *G;
public:
NodeRange(const Graph &G) : G(&G) {}
NodeRange() : G(nullptr) {};
~NodeRange() = default;
NodeIterator begin() const noexcept {
assert(G);
return NodeIterator(G, node{0});
}
NodeIterator end() const noexcept {
assert(G);
return NodeIterator(G, G->upperNodeIdBound());
}
};
// Necessary for friendship with EdgeIteratorBase.
class EdgeIterator;
class EdgeWeightIterator;
class EdgeIteratorBase {
friend class EdgeIterator;
friend class EdgeWeightIterator;
const Graph *G;
NodeIterator nodeIter;
index i;
bool validEdge() const noexcept {
return G->isDirected() || (*nodeIter <= G->outEdges[*nodeIter][i]);
}
void nextEdge() {
do {
if (++i >= G->degree(*nodeIter)) {
i = 0;
do {
assert(nodeIter != G->nodeRange().end());
++nodeIter;
if (nodeIter == G->nodeRange().end()) {
return;
}
} while (!G->degree(*nodeIter));
}
} while (!validEdge());
}
void prevEdge() {
do {
if (!i) {
do {
assert(nodeIter != G->nodeRange().begin());
--nodeIter;
} while (!G->degree(*nodeIter));
i = G->degree(*nodeIter);
}
--i;
} while (!validEdge());
}
EdgeIteratorBase(const Graph *G, NodeIterator nodeIter)
: G(G), nodeIter(nodeIter), i(index{0}) {
if (nodeIter != G->nodeRange().end() && !G->degree(*nodeIter)) {
nextEdge();
}
}
EdgeIteratorBase() : G(nullptr) {}
virtual ~EdgeIteratorBase() = default;
bool operator==(const EdgeIteratorBase &rhs) const noexcept {
return nodeIter == rhs.nodeIter && i == rhs.i;
}
bool operator!=(const EdgeIteratorBase &rhs) const noexcept { return !(*this == rhs); }
};
class EdgeIterator : public EdgeIteratorBase {
public:
// The value type of the edges (i.e. a pair). Returned by operator*().
using value_type = Edge;
// Reference to the value_type, required by STL.
using reference = value_type &;
// Pointer to the value_type, required by STL.
using pointer = value_type *;
// STL iterator category.
using iterator_category = std::forward_iterator_tag;
// Signed integer type of the result of subtracting two pointers,
// required by STL.
using difference_type = ptrdiff_t;
// Own type.
using self = EdgeIterator;
EdgeIterator(const Graph *G, NodeIterator nodeIter) : EdgeIteratorBase(G, nodeIter) {}
EdgeIterator() : EdgeIteratorBase() {}
bool operator==(const EdgeIterator &rhs) const noexcept {
return this->EdgeIteratorBase::operator==(static_cast<EdgeIteratorBase>(rhs));
}
bool operator!=(const EdgeIterator &rhs) const noexcept { return !(*this == rhs); }
Edge operator*() const noexcept {
assert(nodeIter != G->nodeRange().end());
return Edge(*nodeIter, G->outEdges[*nodeIter][i]);
}
EdgeIterator &operator++() {
nextEdge();
return *this;
}
EdgeIterator operator++(int) {
const auto tmp = *this;
++(*this);
return tmp;
}
EdgeIterator operator--() {
prevEdge();
return *this;
}
EdgeIterator operator--(int) {
const auto tmp = *this;
--(*this);
return tmp;
}
};
class EdgeWeightIterator : public EdgeIteratorBase {
public:
// The value type of the edges and their weights (i.e. WeightedEdge). Returned by
// operator*().
using value_type = WeightedEdge;
// Reference to the value_type, required by STL.
using reference = value_type &;
// Pointer to the value_type, required by STL.
using pointer = value_type *;
// STL iterator category.
using iterator_category = std::forward_iterator_tag;
// Signed integer type of the result of subtracting two pointers,
// required by STL.
using difference_type = ptrdiff_t;
// Own type.
using self = EdgeWeightIterator;
EdgeWeightIterator(const Graph *G, NodeIterator nodeIter) : EdgeIteratorBase(G, nodeIter) {}
EdgeWeightIterator() : EdgeIteratorBase() {}
bool operator==(const EdgeWeightIterator &rhs) const noexcept {
return this->EdgeIteratorBase::operator==(static_cast<EdgeIteratorBase>(rhs));
}
bool operator!=(const EdgeWeightIterator &rhs) const noexcept { return !(*this == rhs); }
EdgeWeightIterator &operator++() {
nextEdge();
return *this;
}
EdgeWeightIterator operator++(int) {
const auto tmp = *this;
++(*this);
return tmp;
}
EdgeWeightIterator operator--() {
prevEdge();
return *this;
}
EdgeWeightIterator operator--(int) {
const auto tmp = *this;
--(*this);
return tmp;
}
WeightedEdge operator*() const noexcept {
assert(nodeIter != G->nodeRange().end());
return WeightedEdge(*nodeIter, G->outEdges[*nodeIter][i],
G->isWeighted() ? G->outEdgeWeights[*nodeIter][i] : 1);
}
};
class EdgeRange {
const Graph *G;
public:
EdgeRange(const Graph &G) : G(&G) {}
EdgeRange() : G(nullptr) {};
~EdgeRange() = default;
EdgeIterator begin() const {
assert(G);
return EdgeIterator(G, G->nodeRange().begin());
}
EdgeIterator end() const {
assert(G);
return EdgeIterator(G, G->nodeRange().end());
}
};
class EdgeWeightRange {
const Graph *G;
public:
EdgeWeightRange(const Graph &G) : G(&G) {}
EdgeWeightRange() : G(nullptr) {};
~EdgeWeightRange() = default;
EdgeWeightIterator begin() const {
assert(G);
return EdgeWeightIterator(G, G->nodeRange().begin());
}
EdgeWeightIterator end() const {
assert(G);
return EdgeWeightIterator(G, G->nodeRange().end());
}
};
class NeighborIterator {
std::vector<node>::const_iterator nIter;
public:
// The value type of the neighbors (i.e. nodes). Returned by
// operator*().
using value_type = node;
// Reference to the value_type, required by STL.
using reference = value_type &;
// Pointer to the value_type, required by STL.
using pointer = value_type *;
// STL iterator category.
using iterator_category = std::forward_iterator_tag;
// Signed integer type of the result of subtracting two pointers,
// required by STL.
using difference_type = ptrdiff_t;
// Own type.
using self = NeighborIterator;
NeighborIterator(std::vector<node>::const_iterator nodesIter) : nIter(nodesIter) {}
NeighborIterator() {}
NeighborIterator &operator++() {
++nIter;
return *this;
}
NeighborIterator operator++(int) {
const auto tmp = *this;
++nIter;
return tmp;
}
NeighborIterator operator--() {
const auto tmp = *this;
--nIter;
return tmp;
}
NeighborIterator operator--(int) {
--nIter;
return *this;
}
bool operator==(const NeighborIterator &rhs) const { return nIter == rhs.nIter; }
bool operator!=(const NeighborIterator &rhs) const { return !(nIter == rhs.nIter); }
node operator*() const { return *nIter; }
};
class NeighborWeightIterator {
std::vector<node>::const_iterator nIter;
std::vector<edgeweight>::const_iterator wIter;
public:
// The value type of the neighbors (i.e. nodes). Returned by
// operator*().
using value_type = std::pair<node, edgeweight>;
// Reference to the value_type, required by STL.
using reference = value_type &;
// Pointer to the value_type, required by STL.
using pointer = value_type *;
// STL iterator category.
using iterator_category = std::forward_iterator_tag;
// Signed integer type of the result of subtracting two pointers,
// required by STL.
using difference_type = ptrdiff_t;
// Own type.
using self = NeighborWeightIterator;
NeighborWeightIterator(std::vector<node>::const_iterator nodesIter,
std::vector<edgeweight>::const_iterator weightIter)
: nIter(nodesIter), wIter(weightIter) {}
NeighborWeightIterator() {}
NeighborWeightIterator &operator++() {
++nIter;
++wIter;
return *this;
}
NeighborWeightIterator operator++(int) {
const auto tmp = *this;
++(*this);
return tmp;
}
NeighborWeightIterator operator--() {
--nIter;
--wIter;
return *this;
}
NeighborWeightIterator operator--(int) {
const auto tmp = *this;
--(*this);
return tmp;
}
bool operator==(const NeighborWeightIterator &rhs) const {
return nIter == rhs.nIter && wIter == rhs.wIter;
}
bool operator!=(const NeighborWeightIterator &rhs) const { return !(*this == rhs); }
std::pair<node, edgeweight> operator*() const { return std::make_pair(*nIter, *wIter); }
};
template <bool InEdges = false>
class NeighborRange {
const Graph *G;
node u;
public:
NeighborRange(const Graph &G, node u) : G(&G), u(u) { assert(G.hasNode(u)); };
NeighborRange() : G(nullptr) {};
NeighborIterator begin() const {
assert(G);
return InEdges ? NeighborIterator(G->inEdges[u].begin())
: NeighborIterator(G->outEdges[u].begin());
}
NeighborIterator end() const {
assert(G);
return InEdges ? NeighborIterator(G->inEdges[u].end())
: NeighborIterator(G->outEdges[u].end());
}
};
using OutNeighborRange = NeighborRange<false>;
using InNeighborRange = NeighborRange<true>;
template <bool InEdges = false>
class NeighborWeightRange {
const Graph *G;
node u;
public:
NeighborWeightRange(const Graph &G, node u) : G(&G), u(u) { assert(G.hasNode(u)); };
NeighborWeightRange() : G(nullptr) {};
NeighborWeightIterator begin() const {
assert(G);
return InEdges
? NeighborWeightIterator(G->inEdges[u].begin(), G->inEdgeWeights[u].begin())
: NeighborWeightIterator(G->outEdges[u].begin(),
G->outEdgeWeights[u].begin());
}
NeighborWeightIterator end() const {
assert(G);
return InEdges
? NeighborWeightIterator(G->inEdges[u].end(), G->inEdgeWeights[u].end())
: NeighborWeightIterator(G->outEdges[u].end(), G->outEdgeWeights[u].end());
}
};
using OutNeighborWeightRange = NeighborWeightRange<false>;
using InNeighborWeightRange = NeighborWeightRange<true>;
Graph(count n = 0, bool weighted = false, bool directed = false, bool edgesIndexed = false);
template <class EdgeMerger = std::plus<edgeweight>>
Graph(const Graph &G, bool weighted, bool directed, bool edgesIndexed = false,
EdgeMerger edgeMerger = std::plus<edgeweight>())
: n(G.n), m(G.m), storedNumberOfSelfLoops(G.storedNumberOfSelfLoops), z(G.z),
omega(edgesIndexed ? G.omega : 0), t(G.t), weighted(weighted), directed(directed),
edgesIndexed(edgesIndexed), // edges are not indexed by default
exists(G.exists),
// let the following be empty for the start, we fill them later
inEdges(0), outEdges(0), inEdgeWeights(0), outEdgeWeights(0), inEdgeIds(0), outEdgeIds(0),
// empty node attribute map as last member for this graph
nodeAttributeMap(this), edgeAttributeMap(this) {
if (G.isDirected() == directed) {
// G.inEdges might be empty (if G is undirected), but
// that's fine
inEdges = G.inEdges;
outEdges = G.outEdges;
// copy weights if needed
if (weighted) {
if (G.isWeighted()) {
// just copy from G, again either both graphs are directed or both are
// undirected
inEdgeWeights = G.inEdgeWeights;
outEdgeWeights = G.outEdgeWeights;
} else {
// G has no weights, set defaultEdgeWeight for all edges
if (directed) {
inEdgeWeights.resize(z);
for (node u = 0; u < z; u++) {
inEdgeWeights[u].resize(G.inEdges[u].size(), defaultEdgeWeight);
}
}
outEdgeWeights.resize(z);
for (node u = 0; u < z; u++) {
outEdgeWeights[u].resize(outEdges[u].size(), defaultEdgeWeight);
}
}
}
if (G.hasEdgeIds() && edgesIndexed) {
inEdgeIds = G.inEdgeIds;
outEdgeIds = G.outEdgeIds;
}
} else if (G.isDirected()) {
// G is directed, but we want an undirected graph
// so we need to combine the out and in stuff for every node
outEdges.resize(z);
if (weighted)
outEdgeWeights.resize(z);
if (G.hasEdgeIds() && edgesIndexed)
outEdgeIds.resize(z);
G.balancedParallelForNodes([&](node u) {
// copy both out and in edges into our new outEdges
outEdges[u].reserve(G.outEdges[u].size() + G.inEdges[u].size());
outEdges[u].insert(outEdges[u].end(), G.outEdges[u].begin(), G.outEdges[u].end());
if (weighted) {
if (G.isWeighted()) {
// same for weights
outEdgeWeights[u].reserve(G.outEdgeWeights[u].size()
+ G.inEdgeWeights[u].size());
outEdgeWeights[u].insert(outEdgeWeights[u].end(),
G.outEdgeWeights[u].begin(),
G.outEdgeWeights[u].end());
} else {
// we are undirected, so no need to write anything into inEdgeWeights
outEdgeWeights[u].resize(outEdges[u].size(), defaultEdgeWeight);
}
}
if (G.hasEdgeIds() && edgesIndexed) {
// copy both out and in edges ids into our new outEdgesIds
outEdgeIds[u].reserve(G.outEdgeIds[u].size() + G.inEdgeIds[u].size());
outEdgeIds[u].insert(outEdgeIds[u].end(), G.outEdgeIds[u].begin(),
G.outEdgeIds[u].end());
}
});
G.balancedParallelForNodes([&](node u) {
// this is necessary to avoid multi edges, because both u -> v and v -> u can exist
// in G
count edgeSurplus = 0;
for (count i = 0; i < G.inEdges[u].size(); ++i) {
node v = G.inEdges[u][i];
bool alreadyPresent = false;
for (count j = 0; j < G.outEdges[u].size(); ++j) {
if (v != G.outEdges[u][j])
continue; // the edge already exists as an out edge
alreadyPresent = true;
if (u != v) {
++edgeSurplus;
if (weighted) // we need combine those edges weights when making it a
// single edge
outEdgeWeights[u][j] =
G.isWeighted()
? edgeMerger(G.inEdgeWeights[u][i], G.outEdgeWeights[u][j])
: edgeMerger(defaultEdgeWeight, defaultEdgeWeight);
if (G.hasEdgeIds() && edgesIndexed)
outEdgeIds[u][j] = std::min(G.inEdgeIds[u][i], G.outEdgeIds[u][j]);
}
break;
}
if (!alreadyPresent) { // an equivalent out edge wasn't present so we add it
outEdges[u].push_back(v);
if (weighted)
outEdgeWeights[u].push_back(G.isWeighted() ? G.inEdgeWeights[u][i]
: defaultEdgeWeight);
if (G.hasEdgeIds() && edgesIndexed)
outEdgeIds[u].push_back(G.inEdgeIds[u][i]);
}
}
#pragma omp atomic
m -= edgeSurplus;
});
} else {
// G is not directed, but this copy should be
// generally we can can copy G.out stuff into our in stuff
inEdges = G.outEdges;
outEdges = G.outEdges;
if (weighted) {
if (G.isWeighted()) {
inEdgeWeights = G.outEdgeWeights;
outEdgeWeights = G.outEdgeWeights;
} else {
// initialize both inEdgeWeights and outEdgeWeights with the
// defaultEdgeWeight
inEdgeWeights.resize(z);
for (node u = 0; u < z; ++u) {
inEdgeWeights[u].resize(inEdges[u].size(), defaultEdgeWeight);
}
outEdgeWeights.resize(z);
for (node u = 0; u < z; ++u) {
outEdgeWeights[u].resize(outEdges[u].size(), defaultEdgeWeight);
}
}
}
if (G.hasEdgeIds() && edgesIndexed) {
inEdgeIds = G.outEdgeIds;
outEdgeIds = G.outEdgeIds;
}
}
if (!G.edgesIndexed && edgesIndexed)
indexEdges();
}
Graph(std::initializer_list<WeightedEdge> edges);
Graph(const Graph &other) = default;
Graph(Graph &&other) noexcept = default;
~Graph() = default;
Graph &operator=(Graph &&other) noexcept = default;
Graph &operator=(const Graph &other) = default;
void preallocateUndirected(node u, size_t size);
void preallocateDirected(node u, size_t outSize, size_t inSize);
void preallocateDirectedOutEdges(node u, size_t outSize);
void preallocateDirectedInEdges(node u, size_t inSize);
void indexEdges(bool force = false);
bool hasEdgeIds() const noexcept { return edgesIndexed; }
edgeid edgeId(node u, node v) const;
std::pair<node, node> edgeById(index id) const {
std::pair<node, node> result{none, none};
bool found = false;
forNodesWhile([&] { return !found; },
[&](node u) {
forNeighborsOf(u, [&](node v) {
if (!this->isDirected() && v < u)
return;
auto uvId = edgeId(u, v);
if (uvId == id) {
found = true;
result = std::make_pair(u, v);
}
});
});
return result;
}
index upperEdgeIdBound() const noexcept { return omega; }
void shrinkToFit();
void TLX_DEPRECATED(compactEdges());
void sortEdges();
template <class Lambda>
void sortEdges(Lambda lambda);
void setEdgeCount(Unsafe, count edges) { m = edges; }
void setUpperEdgeIdBound(Unsafe, edgeid newBound) { omega = newBound; }
void setNumberOfSelfLoops(Unsafe, count loops) { storedNumberOfSelfLoops = loops; }
/* NODE MODIFIERS */
node addNode();
node addNodes(count numberOfNewNodes);
void removeNode(node v);
void removePartialOutEdges(Unsafe, node u) {
assert(hasNode(u));
outEdges[u].clear();
if (isWeighted()) {
outEdgeWeights[u].clear();
}
if (hasEdgeIds()) {
outEdgeIds[u].clear();
}
}
void removePartialInEdges(Unsafe, node u) {
assert(hasNode(u));
inEdges[u].clear();
if (isWeighted()) {
inEdgeWeights[u].clear();
}
if (hasEdgeIds()) {
inEdgeIds[u].clear();
}
}
bool hasNode(node v) const noexcept { return (v < z) && this->exists[v]; }
void restoreNode(node v);
count degree(node v) const {
assert(hasNode(v));
return outEdges[v].size();
}
count degreeIn(node v) const {
assert(hasNode(v));
return directed ? inEdges[v].size() : outEdges[v].size();
}
count degreeOut(node v) const { return degree(v); }
bool isIsolated(node v) const {
if (!exists[v])
throw std::runtime_error("Error, the node does not exist!");
return outEdges[v].empty() && (!directed || inEdges[v].empty());
}
edgeweight weightedDegree(node u, bool countSelfLoopsTwice = false) const;
edgeweight weightedDegreeIn(node u, bool countSelfLoopsTwice = false) const;
/* EDGE MODIFIERS */
bool addEdge(node u, node v, edgeweight ew = defaultEdgeWeight, bool checkMultiEdge = false);
bool addPartialEdge(Unsafe, node u, node v, edgeweight ew = defaultEdgeWeight,
uint64_t index = 0, bool checkForMultiEdges = false);
bool addPartialInEdge(Unsafe, node u, node v, edgeweight ew = defaultEdgeWeight,
uint64_t index = 0, bool checkForMultiEdges = false);
bool addPartialOutEdge(Unsafe, node u, node v, edgeweight ew = defaultEdgeWeight,
uint64_t index = 0, bool checkForMultiEdges = false);
void setKeepEdgesSorted(bool sorted = true) { maintainSortedEdges = sorted; }
void setMaintainCompactEdges(bool compact = true) { maintainCompactEdges = compact; }
bool getKeepEdgesSorted() const noexcept { return maintainSortedEdges; }
/*
* Returns true if edges are currently being compacted when removeEdge() is called.
*/
bool getMaintainCompactEdges() const noexcept { return maintainCompactEdges; }
void removeEdge(node u, node v);
void removeAllEdges();
template <typename Condition>
std::pair<count, count> removeAdjacentEdges(node u, Condition condition, bool edgesIn = false);
void removeSelfLoops();
void removeMultiEdges();
void swapEdge(node s1, node t1, node s2, node t2);
bool hasEdge(node u, node v) const noexcept;
/* GLOBAL PROPERTIES */
bool isWeighted() const noexcept { return weighted; }
bool isDirected() const noexcept { return directed; }
bool isEmpty() const noexcept { return !n; }
count numberOfNodes() const noexcept { return n; }
count numberOfEdges() const noexcept { return m; }
count numberOfSelfLoops() const noexcept { return storedNumberOfSelfLoops; }
index upperNodeIdBound() const noexcept { return z; }
bool checkConsistency() const;
/* DYNAMICS */
void timeStep() {
WARN("Graph::timeStep should not be used and will be deprecated in the future.");
t++;
}
count time() {
WARN("Graph::time should not be used and will be deprecated in the future.");
return t;
}
edgeweight weight(node u, node v) const;
void setWeight(node u, node v, edgeweight ew);
void setWeightAtIthNeighbor(Unsafe, node u, index i, edgeweight ew);
void setWeightAtIthInNeighbor(Unsafe, node u, index i, edgeweight ew);
void increaseWeight(node u, node v, edgeweight ew);
/* SUMS */
edgeweight totalEdgeWeight() const noexcept;
NodeRange nodeRange() const noexcept { return NodeRange(*this); }
EdgeRange edgeRange() const noexcept { return EdgeRange(*this); }
EdgeWeightRange edgeWeightRange() const noexcept { return EdgeWeightRange(*this); }
NeighborRange<false> neighborRange(node u) const {
assert(exists[u]);
return NeighborRange<false>(*this, u);
}
NeighborWeightRange<false> weightNeighborRange(node u) const {
assert(isWeighted());
assert(exists[u]);
return NeighborWeightRange<false>(*this, u);
}
NeighborRange<true> inNeighborRange(node u) const {
assert(isDirected());
assert(exists[u]);
return NeighborRange<true>(*this, u);
}
NeighborWeightRange<true> weightInNeighborRange(node u) const {
assert(isDirected() && isWeighted());
assert(exists[u]);
return NeighborWeightRange<true>(*this, u);
}
index indexOfNeighbor(node u, node v) const { return indexInOutEdgeArray(u, v); }
node getIthNeighbor(node u, index i) const {
if (!hasNode(u) || i >= outEdges[u].size())
return none;
return outEdges[u][i];
}
node getIthInNeighbor(node u, index i) const {
if (!hasNode(u) || i >= inEdges[u].size())
return none;
return inEdges[u][i];
}
edgeweight getIthNeighborWeight(node u, index i) const {
if (!hasNode(u) || i >= outEdges[u].size())
return nullWeight;
return isWeighted() ? outEdgeWeights[u][i] : defaultEdgeWeight;
}
std::pair<node, edgeweight> getIthNeighborWithWeight(node u, index i) const {
if (!hasNode(u) || i >= outEdges[u].size())
return {none, none};
return getIthNeighborWithWeight(unsafe, u, i);
}
std::pair<node, edgeweight> getIthNeighborWithWeight(Unsafe, node u, index i) const {
if (!isWeighted())
return {outEdges[u][i], defaultEdgeWeight};
return {outEdges[u][i], outEdgeWeights[u][i]};
}
std::pair<node, edgeid> getIthNeighborWithId(node u, index i) const {
assert(hasEdgeIds());
if (!hasNode(u) || i >= outEdges[u].size())
return {none, none};
return {outEdges[u][i], outEdgeIds[u][i]};
}
/* NODE ITERATORS */
template <typename L>
void forNodes(L handle) const;
template <typename L>
void parallelForNodes(L handle) const;
template <typename C, typename L>
void forNodesWhile(C condition, L handle) const;
template <typename L>
void forNodesInRandomOrder(L handle) const;
template <typename L>
void balancedParallelForNodes(L handle) const;
template <typename L>
void forNodePairs(L handle) const;
template <typename L>
void parallelForNodePairs(L handle) const;
/* EDGE ITERATORS */
template <typename L>
void forEdges(L handle) const;
template <typename L>
void parallelForEdges(L handle) const;
/* NEIGHBORHOOD ITERATORS */
template <typename L>
void forNeighborsOf(node u, L handle) const;
template <typename L>
void forEdgesOf(node u, L handle) const;
template <typename L>
void forInNeighborsOf(node u, L handle) const;
template <typename L>
void forInEdgesOf(node u, L handle) const;
/* REDUCTION ITERATORS */
template <typename L>
double parallelSumForNodes(L handle) const;
template <typename L>
double parallelSumForEdges(L handle) const;
};
/* NODE ITERATORS */
template <typename L>
void Graph::forNodes(L handle) const {
for (node v = 0; v < z; ++v) {
if (exists[v]) {
handle(v);
}
}
}
template <typename L>
void Graph::parallelForNodes(L handle) const {
#pragma omp parallel for
for (omp_index v = 0; v < static_cast<omp_index>(z); ++v) {
if (exists[v]) {
handle(v);
}
}
}
template <typename C, typename L>
void Graph::forNodesWhile(C condition, L handle) const {
for (node v = 0; v < z; ++v) {
if (exists[v]) {
if (!condition()) {
break;
}
handle(v);
}
}
}
template <typename L>
void Graph::forNodesInRandomOrder(L handle) const {
std::vector<node> randVec;
randVec.reserve(numberOfNodes());
forNodes([&](node u) { randVec.push_back(u); });
std::shuffle(randVec.begin(), randVec.end(), Aux::Random::getURNG());
for (node v : randVec) {
handle(v);
}
}
template <typename L>
void Graph::balancedParallelForNodes(L handle) const {
// TODO: define min block size (and test it!)
#pragma omp parallel for schedule(guided)
for (omp_index v = 0; v < static_cast<omp_index>(z); ++v) {
if (exists[v]) {
handle(v);
}
}
}
template <typename L>
void Graph::forNodePairs(L handle) const {
for (node u = 0; u < z; ++u) {
if (exists[u]) {
for (node v = u + 1; v < z; ++v) {
if (exists[v]) {
handle(u, v);
}
}
}
}
}
template <typename L>
void Graph::parallelForNodePairs(L handle) const {
#pragma omp parallel for schedule(guided)
for (omp_index u = 0; u < static_cast<omp_index>(z); ++u) {
if (exists[u]) {
for (node v = u + 1; v < z; ++v) {
if (exists[v]) {
handle(u, v);
}
}
}
}
}
/* EDGE ITERATORS */
/* HELPERS */
template <typename T>
void erase(node u, index idx, std::vector<std::vector<T>> &vec);
// implementation for weighted == true
template <bool hasWeights>
inline edgeweight Graph::getOutEdgeWeight(node u, index i) const {
return outEdgeWeights[u][i];
}
// implementation for weighted == false
template <>
inline edgeweight Graph::getOutEdgeWeight<false>(node, index) const {
return defaultEdgeWeight;
}
// implementation for weighted == true
template <bool hasWeights>
inline edgeweight Graph::getInEdgeWeight(node u, index i) const {
return inEdgeWeights[u][i];
}
// implementation for weighted == false
template <>
inline edgeweight Graph::getInEdgeWeight<false>(node, index) const {
return defaultEdgeWeight;
}
// implementation for hasEdgeIds == true
template <bool graphHasEdgeIds>
inline edgeid Graph::getOutEdgeId(node u, index i) const {
return outEdgeIds[u][i];
}
// implementation for hasEdgeIds == false
template <>
inline edgeid Graph::getOutEdgeId<false>(node, index) const {
return none;
}
// implementation for hasEdgeIds == true
template <bool graphHasEdgeIds>
inline edgeid Graph::getInEdgeId(node u, index i) const {
return inEdgeIds[u][i];
}
// implementation for hasEdgeIds == false
template <>
inline edgeid Graph::getInEdgeId<false>(node, index) const {
return none;
}
// implementation for graphIsDirected == true
template <bool graphIsDirected>
inline bool Graph::useEdgeInIteration(node /* u */, node /* v */) const {
return true;
}
// implementation for graphIsDirected == false
template <>
inline bool Graph::useEdgeInIteration<false>(node u, node v) const {
return u >= v;
}
template <bool graphIsDirected, bool hasWeights, bool graphHasEdgeIds, typename L>
inline void Graph::forOutEdgesOfImpl(node u, L handle) const {
for (index i = 0; i < outEdges[u].size(); ++i) {
node v = outEdges[u][i];
if (useEdgeInIteration<graphIsDirected>(u, v)) {
edgeLambda<L>(handle, u, v, getOutEdgeWeight<hasWeights>(u, i),
getOutEdgeId<graphHasEdgeIds>(u, i));
}
}
}
template <bool graphIsDirected, bool hasWeights, bool graphHasEdgeIds, typename L>
inline void Graph::forInEdgesOfImpl(node u, L handle) const {
if (graphIsDirected) {
for (index i = 0; i < inEdges[u].size(); i++) {
node v = inEdges[u][i];
if (useEdgeInIteration<true>(u, v)) {
edgeLambda<L>(handle, u, v, getInEdgeWeight<hasWeights>(u, i),
getInEdgeId<graphHasEdgeIds>(u, i));
}
}
} else {
for (index i = 0; i < outEdges[u].size(); ++i) {
node v = outEdges[u][i];
if (useEdgeInIteration<true>(u, v)) {
edgeLambda<L>(handle, u, v, getOutEdgeWeight<hasWeights>(u, i),
getOutEdgeId<graphHasEdgeIds>(u, i));
}
}
}
}
template <bool graphIsDirected, bool hasWeights, bool graphHasEdgeIds, typename L>
inline void Graph::forEdgeImpl(L handle) const {
for (node u = 0; u < z; ++u) {
forOutEdgesOfImpl<graphIsDirected, hasWeights, graphHasEdgeIds, L>(u, handle);
}
}
template <bool graphIsDirected, bool hasWeights, bool graphHasEdgeIds, typename L>
inline void Graph::parallelForEdgesImpl(L handle) const {
#pragma omp parallel for schedule(guided)
for (omp_index u = 0; u < static_cast<omp_index>(z); ++u) {
forOutEdgesOfImpl<graphIsDirected, hasWeights, graphHasEdgeIds, L>(u, handle);
}
}
template <bool graphIsDirected, bool hasWeights, bool graphHasEdgeIds, typename L>
inline double Graph::parallelSumForEdgesImpl(L handle) const {
double sum = 0.0;
#pragma omp parallel for reduction(+ : sum)
for (omp_index u = 0; u < static_cast<omp_index>(z); ++u) {
for (index i = 0; i < outEdges[u].size(); ++i) {
node v = outEdges[u][i];
// undirected, do not iterate over edges twice
// {u, v} instead of (u, v); if v == none, u > v is not fulfilled
if (useEdgeInIteration<graphIsDirected>(u, v)) {
sum += edgeLambda<L>(handle, u, v, getOutEdgeWeight<hasWeights>(u, i),
getOutEdgeId<graphHasEdgeIds>(u, i));
}
}
}
return sum;
}
template <typename L>
void Graph::forEdges(L handle) const {
switch (weighted + 2 * directed + 4 * edgesIndexed) {
case 0: // unweighted, undirected, no edgeIds
forEdgeImpl<false, false, false, L>(handle);
break;
case 1: // weighted, undirected, no edgeIds
forEdgeImpl<false, true, false, L>(handle);
break;
case 2: // unweighted, directed, no edgeIds
forEdgeImpl<true, false, false, L>(handle);
break;
case 3: // weighted, directed, no edgeIds
forEdgeImpl<true, true, false, L>(handle);
break;
case 4: // unweighted, undirected, with edgeIds
forEdgeImpl<false, false, true, L>(handle);
break;
case 5: // weighted, undirected, with edgeIds
forEdgeImpl<false, true, true, L>(handle);
break;
case 6: // unweighted, directed, with edgeIds
forEdgeImpl<true, false, true, L>(handle);
break;
case 7: // weighted, directed, with edgeIds
forEdgeImpl<true, true, true, L>(handle);
break;
}
}
template <typename L>
void Graph::parallelForEdges(L handle) const {
switch (weighted + 2 * directed + 4 * edgesIndexed) {
case 0: // unweighted, undirected, no edgeIds
parallelForEdgesImpl<false, false, false, L>(handle);
break;
case 1: // weighted, undirected, no edgeIds
parallelForEdgesImpl<false, true, false, L>(handle);
break;
case 2: // unweighted, directed, no edgeIds
parallelForEdgesImpl<true, false, false, L>(handle);
break;
case 3: // weighted, directed, no edgeIds
parallelForEdgesImpl<true, true, false, L>(handle);
break;
case 4: // unweighted, undirected, with edgeIds
parallelForEdgesImpl<false, false, true, L>(handle);
break;
case 5: // weighted, undirected, with edgeIds
parallelForEdgesImpl<false, true, true, L>(handle);
break;
case 6: // unweighted, directed, with edgeIds
parallelForEdgesImpl<true, false, true, L>(handle);
break;
case 7: // weighted, directed, with edgeIds
parallelForEdgesImpl<true, true, true, L>(handle);
break;
}
}
/* NEIGHBORHOOD ITERATORS */
template <typename L>
void Graph::forNeighborsOf(node u, L handle) const {
forEdgesOf(u, handle);
}
template <typename L>
void Graph::forEdgesOf(node u, L handle) const {
switch (weighted + 2 * edgesIndexed) {
case 0: // not weighted, no edge ids
forOutEdgesOfImpl<true, false, false, L>(u, handle);
break;
case 1: // weighted, no edge ids
forOutEdgesOfImpl<true, true, false, L>(u, handle);
break;
case 2: // not weighted, with edge ids
forOutEdgesOfImpl<true, false, true, L>(u, handle);
break;
case 3: // weighted, with edge ids
forOutEdgesOfImpl<true, true, true, L>(u, handle);
break;
}
}
template <typename L>
void Graph::forInNeighborsOf(node u, L handle) const {
forInEdgesOf(u, handle);
}
template <typename L>
void Graph::forInEdgesOf(node u, L handle) const {
switch (weighted + 2 * directed + 4 * edgesIndexed) {
case 0: // unweighted, undirected, no edge ids
forInEdgesOfImpl<false, false, false, L>(u, handle);
break;
case 1: // weighted, undirected, no edge ids
forInEdgesOfImpl<false, true, false, L>(u, handle);
break;
case 2: // unweighted, directed, no edge ids
forInEdgesOfImpl<true, false, false, L>(u, handle);
break;
case 3: // weighted, directed, no edge ids
forInEdgesOfImpl<true, true, false, L>(u, handle);
break;
case 4: // unweighted, undirected, with edge ids
forInEdgesOfImpl<false, false, true, L>(u, handle);
break;
case 5: // weighted, undirected, with edge ids
forInEdgesOfImpl<false, true, true, L>(u, handle);
break;
case 6: // unweighted, directed, with edge ids
forInEdgesOfImpl<true, false, true, L>(u, handle);
break;
case 7: // weighted, directed, with edge ids
forInEdgesOfImpl<true, true, true, L>(u, handle);
break;
}
}
/* REDUCTION ITERATORS */
template <typename L>
double Graph::parallelSumForNodes(L handle) const {
double sum = 0.0;
#pragma omp parallel for reduction(+ : sum)
for (omp_index v = 0; v < static_cast<omp_index>(z); ++v) {
if (exists[v]) {
sum += handle(v);
}
}
return sum;
}
template <typename L>
double Graph::parallelSumForEdges(L handle) const {
double sum = 0.0;
switch (weighted + 2 * directed + 4 * edgesIndexed) {
case 0: // unweighted, undirected, no edge ids
sum = parallelSumForEdgesImpl<false, false, false, L>(handle);
break;
case 1: // weighted, undirected, no edge ids
sum = parallelSumForEdgesImpl<false, true, false, L>(handle);
break;
case 2: // unweighted, directed, no edge ids
sum = parallelSumForEdgesImpl<true, false, false, L>(handle);
break;
case 3: // weighted, directed, no edge ids
sum = parallelSumForEdgesImpl<true, true, false, L>(handle);
break;
case 4: // unweighted, undirected, with edge ids
sum = parallelSumForEdgesImpl<false, false, true, L>(handle);
break;
case 5: // weighted, undirected, with edge ids
sum = parallelSumForEdgesImpl<false, true, true, L>(handle);
break;
case 6: // unweighted, directed, with edge ids
sum = parallelSumForEdgesImpl<true, false, true, L>(handle);
break;
case 7: // weighted, directed, with edge ids
sum = parallelSumForEdgesImpl<true, true, true, L>(handle);
break;
}
return sum;
}
/* EDGE MODIFIERS */
template <typename Condition>
std::pair<count, count> Graph::removeAdjacentEdges(node u, Condition condition, bool edgesIn) {
count removedEdges = 0;
count removedSelfLoops = 0;
// For directed graphs, this function is supposed to be called twice: one to remove out-edges,
// and one to remove in-edges.
auto &edges_ = edgesIn ? inEdges[u] : outEdges[u];
for (index vi = 0; vi < edges_.size();) {
if (condition(edges_[vi])) {
const auto isSelfLoop = (edges_[vi] == u);
removedSelfLoops += isSelfLoop;
removedEdges += !isSelfLoop;
edges_[vi] = edges_.back();
edges_.pop_back();
if (isWeighted()) {
auto &weights_ = edgesIn ? inEdgeWeights[u] : outEdgeWeights[u];
weights_[vi] = weights_.back();
weights_.pop_back();
}
if (hasEdgeIds()) {
auto &edgeIds_ = edgesIn ? inEdgeIds[u] : outEdgeIds[u];
edgeIds_[vi] = edgeIds_.back();
edgeIds_.pop_back();
}
} else {
++vi;
}
}
return {removedEdges, removedSelfLoops};
}
template <class Lambda>
void Graph::sortEdges(Lambda lambda) {
std::vector<std::vector<index>> indicesGlobal(omp_get_max_threads());
const auto sortAdjacencyArrays = [&](node u, std::vector<node> &adjList,
std::vector<edgeweight> &weights,
std::vector<edgeid> &edgeIds) -> void {
auto &indices = indicesGlobal[omp_get_thread_num()];
if (adjList.size() > indices.size())
indices.resize(adjList.size());
const auto indicesEnd =
indices.begin()
+ static_cast<
std::iterator_traits<std::vector<index>::const_iterator>::difference_type>(
adjList.size());
std::iota(indices.begin(), indicesEnd, 0);
if (isWeighted()) {
if (hasEdgeIds())
std::sort(indices.begin(), indicesEnd, [&](auto a, auto b) -> bool {
return lambda(WeightedEdgeWithId{u, adjList[a], weights[a], edgeIds[a]},
WeightedEdgeWithId{u, adjList[b], weights[b], edgeIds[b]});
});
else
std::sort(indices.begin(), indicesEnd, [&](auto a, auto b) -> bool {
return lambda(WeightedEdgeWithId{u, adjList[a], weights[a], 0},
WeightedEdgeWithId{u, adjList[b], weights[b], 0});
});
} else if (hasEdgeIds())
std::sort(indices.begin(), indicesEnd, [&](auto a, auto b) -> bool {
return lambda(WeightedEdgeWithId{u, adjList[a], defaultEdgeWeight, edgeIds[a]},
WeightedEdgeWithId{u, adjList[b], defaultEdgeWeight, edgeIds[b]});
});
else
std::sort(indices.begin(), indicesEnd, [&](auto a, auto b) -> bool {
return lambda(WeightedEdgeWithId{u, adjList[a], defaultEdgeWeight, 0},
WeightedEdgeWithId{u, adjList[b], defaultEdgeWeight, 0});
});
Aux::ArrayTools::applyPermutation(adjList.begin(), adjList.end(), indices.begin());
if (isWeighted())
Aux::ArrayTools::applyPermutation(weights.begin(), weights.end(), indices.begin());
if (hasEdgeIds())
Aux::ArrayTools::applyPermutation(edgeIds.begin(), edgeIds.end(), indices.begin());
};
balancedParallelForNodes([&](const node u) {
if (degree(u) < 2)
return;
std::vector<edgeweight> dummyEdgeWeights;
std::vector<edgeid> dummyEdgeIds;
sortAdjacencyArrays(u, outEdges[u], isWeighted() ? outEdgeWeights[u] : dummyEdgeWeights,
hasEdgeIds() ? outEdgeIds[u] : dummyEdgeIds);
if (isDirected())
sortAdjacencyArrays(u, inEdges[u], isWeighted() ? inEdgeWeights[u] : dummyEdgeWeights,
hasEdgeIds() ? inEdgeIds[u] : dummyEdgeIds);
});
}
} /* namespace NetworKit */
#endif // NETWORKIT_GRAPH_GRAPH_HPP_