Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions include/kaminpar-shm/kaminpar.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ enum class TieBreakingStrategy {
UNIFORM,
};

enum class LabelPropagationFastMode {
OFF,
LIGHT,
AGGRESSIVE,
};

enum class ContractionAlgorithm {
BUFFERED,
UNBUFFERED,
Expand All @@ -151,6 +157,7 @@ struct LabelPropagationCoarseningContext {
IsolatedNodesClusteringStrategy isolated_nodes_strategy;

TieBreakingStrategy tie_breaking_strategy;
LabelPropagationFastMode fast_mode;
};

struct ContractionCoarseningContext {
Expand Down
10 changes: 10 additions & 0 deletions kaminpar-cli/kaminpar_arguments.cc
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,16 @@ CLI::Option_group *create_lp_coarsening_options(CLI::App *app, Context &ctx) {
R"(Chooses the tie breaking strategy:
- geometric: Prefer nodes with same rating located at the end of a neighborhood.
- uniform: Select nodes with same rating uniformly at random.
)"
)
->capture_default_str();
lp->add_option("--c-lp-fast-mode", ctx.coarsening.clustering.lp.fast_mode)
->transform(CLI::CheckedTransformer(get_lp_fast_modes()).description(""))
->description(
R"(Chooses an opt-in speed/quality tradeoff for coarsening LP:
- off: Preserve the configured LP behavior.
- light: Use single-phase LP with 3 iterations, geometric tie breaking and at most 512 neighbors.
- aggressive: Use single-phase LP with 2 iterations, geometric tie breaking, at most 256 neighbors and skip very high-degree nodes.
)"
)
->capture_default_str();
Expand Down
2 changes: 1 addition & 1 deletion kaminpar-common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ GetModifiedGitFiles(MODIFIED_FILES)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/environment.cc.in" "${CMAKE_CURRENT_BINARY_DIR}/environment.cc" @ONLY)

file(GLOB_RECURSE KAMINPAR_COMMON_SOURCE_FILES CONFIGURE_DEPENDS
*.cc *.h)
*.cc *.h *.inc)

add_library(KaMinParCommon OBJECT ${KAMINPAR_COMMON_SOURCE_FILES} "${CMAKE_CURRENT_BINARY_DIR}/environment.cc")
add_library(KaMinPar::KaMinParCommon ALIAS KaMinParCommon)
Expand Down
23 changes: 23 additions & 0 deletions kaminpar-common/algorithms/label_propagation.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*******************************************************************************
* Composable label propagation building blocks.
*
* @file: label_propagation.h
******************************************************************************/
#pragma once

#include "kaminpar-common/datastructures/concurrent_fast_reset_array.h"
#include "kaminpar-common/datastructures/dynamic_map.h"
#include "kaminpar-common/datastructures/rating_map.h"
#include "kaminpar-common/label_propagation/active_set.h"
#include "kaminpar-common/label_propagation/cluster_chooser.h"
#include "kaminpar-common/label_propagation/kernel.h"
#include "kaminpar-common/label_propagation/move.h"
#include "kaminpar-common/label_propagation/passes/growing_hash_tables.h"
#include "kaminpar-common/label_propagation/passes/single_phase.h"
#include "kaminpar-common/label_propagation/passes/two_phase.h"
#include "kaminpar-common/label_propagation/postprocessing.h"
#include "kaminpar-common/label_propagation/rating_accumulator.h"
#include "kaminpar-common/label_propagation/run.h"
#include "kaminpar-common/label_propagation/stores.h"
#include "kaminpar-common/label_propagation/types.h"
#include "kaminpar-common/label_propagation/workspace.h"
1 change: 1 addition & 0 deletions kaminpar-common/datastructures/dynamic_map.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "kaminpar-common/datastructures/scalable_vector.h"
#include "kaminpar-common/parallel/tbb_malloc.h"
#include "kaminpar-common/ranges.h"

namespace kaminpar {

Expand Down
150 changes: 150 additions & 0 deletions kaminpar-common/label_propagation/active_set.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*******************************************************************************
* Active-set handling for label propagation passes.
*
* @file: active_set.h
******************************************************************************/
#pragma once

#include "kaminpar-common/assert.h"
#include "kaminpar-common/label_propagation/types.h"

namespace kaminpar::lp {

template <typename NodeID, typename Graph, typename NeighborPolicy, typename Workspace>
class ActiveSetView {
public:
ActiveSetView(
const Graph &graph,
NeighborPolicy &neighbors,
Workspace &workspace,
const ActiveSetConfig &config
)
: _graph(graph),
_neighbors(neighbors),
_workspace(workspace),
_config(config) {}

KAMINPAR_INLINE void initialize_node(const NodeID u) {
if (_config.strategy != ActiveSetStrategy::NONE) {
_workspace.active_set.flags[u] = 1;
}
}

[[nodiscard]] KAMINPAR_INLINE bool is_active(const NodeID u) const {
return _config.strategy == ActiveSetStrategy::NONE ||
(u < _workspace.active_set.flags.size() &&
__atomic_load_n(&_workspace.active_set.flags[u], __ATOMIC_RELAXED));
}

template <ActiveSetStrategy Strategy>
[[nodiscard]] KAMINPAR_INLINE bool is_active(const NodeID u) const {
if constexpr (Strategy == ActiveSetStrategy::NONE) {
return true;
} else {
KASSERT(u < _workspace.active_set.flags.size());
return __atomic_load_n(&_workspace.active_set.flags[u], __ATOMIC_RELAXED);
}
}

KAMINPAR_INLINE void clear(const NodeID u, const bool is_interface_node) {
if (_config.strategy == ActiveSetStrategy::GLOBAL) {
__atomic_store_n(&_workspace.active_set.flags[u], 0, __ATOMIC_RELAXED);
} else if (_config.strategy == ActiveSetStrategy::LOCAL && !is_interface_node) {
__atomic_store_n(&_workspace.active_set.flags[u], 0, __ATOMIC_RELAXED);
}
}

template <ActiveSetStrategy Strategy>
KAMINPAR_INLINE void clear(const NodeID u, const bool is_interface_node) {
if constexpr (Strategy == ActiveSetStrategy::GLOBAL) {
__atomic_store_n(&_workspace.active_set.flags[u], 0, __ATOMIC_RELAXED);
} else if constexpr (Strategy == ActiveSetStrategy::LOCAL) {
if (!is_interface_node) {
__atomic_store_n(&_workspace.active_set.flags[u], 0, __ATOMIC_RELAXED);
}
}
}

KAMINPAR_INLINE void activate_neighbors(const NodeID u) {
if (_config.strategy == ActiveSetStrategy::NONE || !_config.activate_neighbors) {
return;
}

_graph.adjacent_nodes(u, [&](const NodeID v) {
if (_neighbors.activate(v) && v < _workspace.active_set.flags.size()) {
__atomic_store_n(&_workspace.active_set.flags[v], 1, __ATOMIC_RELAXED);
}
});
}

template <ActiveSetStrategy Strategy> KAMINPAR_INLINE void activate_neighbors(const NodeID u) {
if constexpr (Strategy != ActiveSetStrategy::NONE) {
if (!_config.activate_neighbors) {
return;
}

if constexpr (requires {
_graph.raw_nodes();
_graph.raw_edges();
}) {
const auto &nodes = _graph.raw_nodes();
const auto &edges = _graph.raw_edges();
const auto to = nodes[u + 1];
for (auto edge = nodes[u]; edge < to; ++edge) {
const NodeID v = edges[edge];
if constexpr (!ActivatesAllNeighbors<NeighborPolicy>::value) {
if (!_neighbors.activate(v)) {
continue;
}
}

if constexpr (requires { _graph.is_ghost_node(v); }) {
if (v < _workspace.active_set.flags.size()) {
__atomic_store_n(&_workspace.active_set.flags[v], 1, __ATOMIC_RELAXED);
}
} else {
__atomic_store_n(&_workspace.active_set.flags[v], 1, __ATOMIC_RELAXED);
}
}
return;
}

_graph.adjacent_nodes(u, [&](const NodeID v) {
if constexpr (!ActivatesAllNeighbors<NeighborPolicy>::value) {
if (!_neighbors.activate(v)) {
return;
}
}

if constexpr (requires { _graph.is_ghost_node(v); }) {
if (v < _workspace.active_set.flags.size()) {
__atomic_store_n(&_workspace.active_set.flags[v], 1, __ATOMIC_RELAXED);
}
} else {
__atomic_store_n(&_workspace.active_set.flags[v], 1, __ATOMIC_RELAXED);
}
});
}
}

KAMINPAR_INLINE void activate_neighbors_of_ghost_node(const NodeID u) {
KASSERT(_graph.is_ghost_node(u));
if (_config.strategy != ActiveSetStrategy::GLOBAL) {
return;
}

_graph.ghost_graph().adjacent_nodes(u, [&](const NodeID v) {
if (_neighbors.activate(v) && v < _workspace.active_set.flags.size()) {
__atomic_store_n(&_workspace.active_set.flags[v], 1, __ATOMIC_RELAXED);
}
});
}

private:
const Graph &_graph;
NeighborPolicy &_neighbors;
Workspace &_workspace;
const ActiveSetConfig &_config;
};

} // namespace kaminpar::lp
159 changes: 159 additions & 0 deletions kaminpar-common/label_propagation/cluster_chooser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*******************************************************************************
* Shared best-cluster selection loop.
*
* @file: cluster_chooser.h
******************************************************************************/
#pragma once

#include "kaminpar-common/datastructures/scalable_vector.h"
#include "kaminpar-common/label_propagation/types.h"

namespace kaminpar::lp {

template <typename EdgeWeight>
[[nodiscard]] KAMINPAR_INLINE CandidateComparison
compare_by_gain(const EdgeWeight candidate_gain, const EdgeWeight best_gain) {
if (candidate_gain > best_gain) {
return CandidateComparison::BETTER;
}
if (candidate_gain == best_gain) {
return CandidateComparison::EQUIVALENT;
}
return CandidateComparison::WORSE;
}

template <typename Context>
[[nodiscard]] KAMINPAR_INLINE auto make_initial_choice(const Context &context) {
using ClusterID = typename Context::ClusterIDType;
using ClusterWeight = typename Context::ClusterWeightType;
using EdgeWeight = typename Context::EdgeWeightType;

return ClusterChoice<ClusterID, ClusterWeight, EdgeWeight>{
.best_cluster = context.initial_cluster,
.best_gain = 0,
.best_cluster_weight = context.initial_cluster_weight,
.favored_cluster = context.initial_cluster,
.favored_gain = 0,
};
}

template <typename Choice, typename Candidate>
KAMINPAR_INLINE void set_best_cluster(Choice &choice, const Candidate &candidate) {
choice.best_cluster = candidate.cluster;
choice.best_gain = candidate.gain;
choice.best_cluster_weight = candidate.weight;
}

template <typename Choice, typename Candidate>
KAMINPAR_INLINE void set_favored_cluster(Choice &choice, const Candidate &candidate) {
choice.favored_cluster = candidate.cluster;
choice.favored_gain = candidate.gain;
}

template <TieBreakingStrategy Strategy, typename Context, typename Evaluator, typename RatingMap>
[[nodiscard]] KAMINPAR_INLINE auto choose_cluster(
const Context &context,
RatingMap &map,
Evaluator &evaluator,
ScalableVector<typename Context::ClusterIDType> &tie_breaking_clusters,
ScalableVector<typename Context::ClusterIDType> &tie_breaking_favored_clusters
) {
using ClusterID = typename Context::ClusterIDType;
using ClusterWeight = typename Context::ClusterWeightType;
using EdgeWeight = typename Context::EdgeWeightType;
using Candidate = ClusterCandidate<ClusterID, ClusterWeight, EdgeWeight>;

auto choice = make_initial_choice(context);

for (const auto [cluster, rating] : map.entries()) {
const Candidate candidate{
.cluster = static_cast<ClusterID>(cluster),
.gain = rating - context.gain_delta,
.weight = evaluator.cluster_weight(cluster),
};

if (context.track_favored_cluster) {
const CandidateComparison favored_comparison =
compare_by_gain(candidate.gain, choice.favored_gain);
if constexpr (Strategy == TieBreakingStrategy::UNIFORM) {
if (favored_comparison == CandidateComparison::BETTER) {
set_favored_cluster(choice, candidate);
tie_breaking_favored_clusters.clear();
tie_breaking_favored_clusters.push_back(candidate.cluster);
} else if (favored_comparison == CandidateComparison::EQUIVALENT) {
tie_breaking_favored_clusters.push_back(candidate.cluster);
}
} else {
if (favored_comparison == CandidateComparison::BETTER) {
set_favored_cluster(choice, candidate);
}
}
}

const CandidateComparison comparison = evaluator.compare(context, candidate, choice);
if constexpr (Strategy == TieBreakingStrategy::UNIFORM) {
if (comparison == CandidateComparison::BETTER) {
if (evaluator.is_feasible(context, candidate, choice)) {
set_best_cluster(choice, candidate);
tie_breaking_clusters.clear();
tie_breaking_clusters.push_back(candidate.cluster);
}
} else if (comparison == CandidateComparison::EQUIVALENT) {
if (evaluator.is_feasible(context, candidate, choice)) {
tie_breaking_clusters.push_back(candidate.cluster);
}
}
} else {
if (comparison == CandidateComparison::BETTER ||
(comparison == CandidateComparison::EQUIVALENT && context.rand.random_bool())) {
if (evaluator.is_feasible(context, candidate, choice)) {
set_best_cluster(choice, candidate);
}
}
}
}

if constexpr (Strategy == TieBreakingStrategy::UNIFORM) {
if (tie_breaking_clusters.size() > 1) {
const ClusterID i = context.rand.random_index(0, tie_breaking_clusters.size());
choice.best_cluster = tie_breaking_clusters[i];
}
tie_breaking_clusters.clear();

if (tie_breaking_favored_clusters.size() > 1) {
const ClusterID i = context.rand.random_index(0, tie_breaking_favored_clusters.size());
choice.favored_cluster = tie_breaking_favored_clusters[i];
}
tie_breaking_favored_clusters.clear();
}

if constexpr (requires { evaluator.record_choice(context, choice); }) {
evaluator.record_choice(context, choice);
}

return choice;
}

template <typename Context, typename Evaluator, typename RatingMap>
[[nodiscard]] KAMINPAR_INLINE auto choose_cluster(
const Context &context,
RatingMap &map,
Evaluator &evaluator,
const TieBreakingStrategy tie_breaking,
ScalableVector<typename Context::ClusterIDType> &tie_breaking_clusters,
ScalableVector<typename Context::ClusterIDType> &tie_breaking_favored_clusters
) {
switch (tie_breaking) {
case TieBreakingStrategy::GEOMETRIC:
return choose_cluster<TieBreakingStrategy::GEOMETRIC>(
context, map, evaluator, tie_breaking_clusters, tie_breaking_favored_clusters
);
case TieBreakingStrategy::UNIFORM:
return choose_cluster<TieBreakingStrategy::UNIFORM>(
context, map, evaluator, tie_breaking_clusters, tie_breaking_favored_clusters
);
}
__builtin_unreachable();
}

} // namespace kaminpar::lp
Loading
Loading