diff --git a/mt-kahypar/partition/factories.h b/mt-kahypar/partition/factories.h index 09a16ba14..6076d8b3d 100644 --- a/mt-kahypar/partition/factories.h +++ b/mt-kahypar/partition/factories.h @@ -53,7 +53,6 @@ #include "mt-kahypar/partition/refinement/gains/gain_definitions.h" #include "mt-kahypar/partition/refinement/flows/scheduler.h" #include "mt-kahypar/partition/refinement/flows/flow_refiner.h" -#include "mt-kahypar/partition/refinement/rebalancing/rebalancer.h" #include "mt-kahypar/partition/refinement/rebalancing/rebalancer_v2.h" namespace mt_kahypar { @@ -126,11 +125,6 @@ using FlowSchedulerDispatcher = kahypar::meta::StaticMultiDispatchFactory< using RebalancerFactory = kahypar::meta::Factory; -using RebalancerDispatcher = kahypar::meta::StaticMultiDispatchFactory< - Rebalancer, - IRebalancer, - kahypar::meta::Typelist>; - using RebalancerV2Dispatcher = kahypar::meta::StaticMultiDispatchFactory< RebalancerV2, IRebalancer, diff --git a/mt-kahypar/partition/refinement/CMakeLists.txt b/mt-kahypar/partition/refinement/CMakeLists.txt index e3754035c..3cb0d2f8f 100644 --- a/mt-kahypar/partition/refinement/CMakeLists.txt +++ b/mt-kahypar/partition/refinement/CMakeLists.txt @@ -5,7 +5,6 @@ set(RefinementSources fm/global_rollback.cpp fm/sequential_twoway_fm_refiner.cpp label_propagation/label_propagation_refiner.cpp - rebalancing/rebalancer.cpp rebalancing/rebalancer_v2.cpp deterministic/deterministic_label_propagation.cpp flows/refiner_adapter.cpp diff --git a/mt-kahypar/partition/refinement/rebalancing/rebalancer.cpp b/mt-kahypar/partition/refinement/rebalancing/rebalancer.cpp deleted file mode 100644 index 34883e010..000000000 --- a/mt-kahypar/partition/refinement/rebalancing/rebalancer.cpp +++ /dev/null @@ -1,299 +0,0 @@ -/******************************************************************************* - * MIT License - * - * This file is part of Mt-KaHyPar. - * - * Copyright (C) 2020 Lars Gottesbüren - * Copyright (C) 2019 Tobias Heuer - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - ******************************************************************************/ - -#include "mt-kahypar/partition/refinement/rebalancing//rebalancer.h" - - -#include - -#include -#include "tbb/enumerable_thread_specific.h" -#include "tbb/parallel_for.h" - -#include "mt-kahypar/definitions.h" -#include "mt-kahypar/partition/metrics.h" -#include "mt-kahypar/partition/refinement/gains/gain_definitions.h" -#include "mt-kahypar/utils/timer.h" -#include "mt-kahypar/utils/cast.h" - -namespace mt_kahypar { - - template - bool Rebalancer::refineImpl(mt_kahypar_partitioned_hypergraph_t& hypergraph, - const vec&, - Metrics& best_metrics, - double) { - PartitionedHypergraph& phg = utils::cast(hypergraph); - resizeDataStructuresForCurrentK(); - // If partition is imbalanced, rebalancer is activated - bool improvement = false; - if ( !metrics::isBalanced(phg, _context) ) { - _gain.reset(); - - for ( PartitionID block = 0; block < _context.partition.k; ++block ) { - _part_weights[block] = phg.partWeight(block); - } - - // This function is passed as lambda to the changeNodePart function and used - // to calculate the "real" delta of a move (in terms of the used objective function). - auto objective_delta = [&](const SynchronizedEdgeUpdate& sync_update) { - _gain.computeDeltaForHyperedge(sync_update); - }; - - if ( _context.partition.preset_type != PresetType::large_k ) { - // TODO: This code must be optimized to work for large k - vec moves_to_empty_blocks = repairEmptyBlocks(phg); - for (Move& m : moves_to_empty_blocks) { - moveVertex(phg, m.node, m, objective_delta); - } - } - - // We first try to perform moves that does not worsen solution quality of the partition - // Moves that would worsen the solution quality are gathered in a thread local priority queue - // and processed afterwards if partition is still imbalanced - std::atomic idx(0); - tbb::enumerable_thread_specific move_pqs([&] { - return IndexedMovePQ(idx++); - }); - phg.doParallelForAllNodes([&](const HypernodeID& hn) { - const PartitionID from = phg.partID(hn); - if ( phg.isBorderNode(hn) && !phg.isFixed(hn) && - phg.partWeight(from) > _context.partition.max_part_weights[from] ) { - Move rebalance_move = _gain.computeMaxGainMove(phg, hn, true /* rebalance move */); - if ( rebalance_move.gain <= 0 ) { - moveVertex(phg, hn, rebalance_move, objective_delta); - } else if ( rebalance_move.gain != std::numeric_limits::max() ) { - move_pqs.local().pq.emplace(std::move(rebalance_move)); - } else { - // Try to find a move to an non-adjacent block - rebalance_move = _gain.computeMaxGainMove(phg, hn, - true /* rebalance move */, true /* non-adjacent block */ ); - if ( rebalance_move.gain != std::numeric_limits::max() ) { - move_pqs.local().pq.emplace(std::move(rebalance_move)); - } - } - } - }); - - ASSERT([&] { - for ( PartitionID block = 0; block < _context.partition.k; ++block ) { - if ( _part_weights[block] != phg.partWeight(block) ) { - return false; - } - } - return true; - }(), "Rebalancer part weights are wrong"); - - // If partition is still imbalanced, we try execute moves stored into - // the thread local priority queue which could possibly worsen solution quality - if ( !metrics::isBalanced(phg, _context) ) { - - // Initialize minimum gain value of each priority queue - parallel::scalable_vector active_pqs(idx.load(), false); - parallel::scalable_vector min_pq_gain(idx.load(), - std::numeric_limits::max() - MIN_PQ_GAIN_THRESHOLD); - for ( const IndexedMovePQ& idx_pq : move_pqs ) { - if ( !idx_pq.pq.empty() ) { - min_pq_gain[idx_pq.idx] = idx_pq.pq.top().gain; - } - } - - // Function returns minimum gain value of all priority queues - auto global_pq_min_gain = [&](const bool only_active_pqs) { - Gain min_gain = std::numeric_limits::max() - MIN_PQ_GAIN_THRESHOLD; - for ( size_t i = 0; i < min_pq_gain.size(); ++i ) { - if ( (!only_active_pqs || active_pqs[i]) && min_pq_gain[i] < min_gain ) { - min_gain = min_pq_gain[i]; - } - } - return min_gain; - }; - - // We process each priority queue in parallel. When we perform - // a move we make sure that the current minimum gain value of the local - // PQ is within a certain threshold of the global minimum gain value. - // Otherwise, we perform busy waiting until all moves with a better gain - // are processed. - tbb::parallel_for_each(move_pqs, [&](IndexedMovePQ& idx_pq) { - const size_t idx = idx_pq.idx; - MovePQ& pq = idx_pq.pq; - active_pqs[idx] = true; - Gain current_global_min_pq_gain = global_pq_min_gain(false); - while ( !pq.empty() ) { - Move move = pq.top(); - min_pq_gain[idx] = move.gain; - pq.pop(); - - // If the minimum gain value of the local priority queue is not within - // a certain threshold of the global priority queue, we perform busy waiting - // until all moves with a better gain of other pqs are performed. - while ( move.gain > current_global_min_pq_gain + MIN_PQ_GAIN_THRESHOLD ) { - current_global_min_pq_gain = global_pq_min_gain(true); - } - - const PartitionID from = move.from; - if ( phg.partWeight(from) > _context.partition.max_part_weights[from] ) { - Move real_move = _gain.computeMaxGainMove(phg, move.node, true /* rebalance move */); - if ( real_move.gain == std::numeric_limits::max() ) { - // Compute move to non-adjacent block - real_move = _gain.computeMaxGainMove(phg, move.node, - true /* rebalance move */, true /* non-adjacent block */); - } - if ( real_move.gain <= move.gain ) { - moveVertex(phg, real_move.node, real_move, objective_delta); - } else if ( real_move.gain != std::numeric_limits::max() ) { - pq.emplace(std::move(real_move)); - } - } - } - active_pqs[idx] = false; - min_pq_gain[idx] = std::numeric_limits::max() - MIN_PQ_GAIN_THRESHOLD; - }); - } - - // Update metrics statistics - Gain delta = _gain.delta(); - HEAVY_REFINEMENT_ASSERT(best_metrics.quality + delta == metrics::quality(phg, _context), - V(best_metrics.quality) << V(delta) << V(metrics::quality(phg, _context))); - best_metrics.quality += delta; - improvement = delta < 0; - } - return improvement; - } - - template - vec Rebalancer::repairEmptyBlocks(PartitionedHypergraph& phg) { - // First detect if there are any empty blocks. - const size_t k = size_t(_context.partition.k); - boost::dynamic_bitset<> is_empty(k); - vec empty_blocks; - for (size_t i = 0; i < k; ++i) { - if (phg.partWeight(PartitionID(i)) == 0) { - is_empty.set(i, true); - empty_blocks.push_back(PartitionID(i)); - } - } - - vec moves_to_empty_blocks; - - // If so, find the best vertices to move to that block - while (is_empty.any()) { - - tbb::enumerable_thread_specific< vec > ets_scores(k, 0); - - // positive gain values correspond to "good" improvement. MovePQ uses std::greater (MinHeap) - // --> stores worst gains at the top where we can eject them - tbb::enumerable_thread_specific< vec< vec > > ets_best_move(k); - - phg.doParallelForAllNodes([&](const HypernodeID u) { - if ( !phg.isFixed(u) ) { - vec& scores = ets_scores.local(); - vec< vec >& move_proposals = ets_best_move.local(); - - const PartitionID from = phg.partID(u); - Gain unremovable = 0; - for (HyperedgeID e : phg.incidentEdges(u)) { - const HyperedgeWeight edge_weight = phg.edgeWeight(e); - if (phg.pinCountInPart(e, from) > 1) { - unremovable += edge_weight; - } - for (PartitionID i : phg.connectivitySet(e)) { - scores[i] += edge_weight; - } - } - - // maintain thread local priority queues of up to k best gains - for (const PartitionID to : empty_blocks) { - ASSERT(is_empty[to]); - if (to != from && phg.partWeight(from) > phg.nodeWeight(u) - && phg.nodeWeight(u) <= _context.partition.max_part_weights[to]) { - const Gain gain = scores[to] - unremovable; - vec& c = move_proposals[to]; - if (c.size() < k) { - c.push_back(Move { from, to, u, gain }); - std::push_heap(c.begin(), c.end(), MoveGainComparator()); - } else if (c.front().gain < gain) { - std::pop_heap(c.begin(), c.end(), MoveGainComparator()); - c.back() = { from, to, u, gain }; - std::push_heap(c.begin(), c.end(), MoveGainComparator()); - } - } - scores[to] = 0; - } - } - }); - - vec< vec > best_moves_per_part(k); - - for (vec>& tlpq : ets_best_move) { - size_t i = is_empty.find_first(); - while (i != is_empty.npos) { - std::copy(tlpq[i].begin(), tlpq[i].end(), std::back_inserter(best_moves_per_part[i])); - i = is_empty.find_next(i); - } - } - - auto prefer_highest_gain = [&](const Move& lhs, const Move& rhs) { - const HypernodeWeight pwl = phg.partWeight(phg.partID(lhs.node)); - const HypernodeWeight pwr = phg.partWeight(phg.partID(rhs.node)); - return std::tie(lhs.gain, pwl, lhs.node) > std::tie(rhs.gain, pwr, rhs.node); - }; - - auto node_already_used = [&](HypernodeID node) { - return std::any_of(moves_to_empty_blocks.begin(), - moves_to_empty_blocks.end(), - [node](const Move& m) { return m.node == node; } - ); - }; - - size_t i = is_empty.find_first(); - while (i != is_empty.npos) { - vec& c = best_moves_per_part[i]; - std::sort(c.begin(), c.end(), prefer_highest_gain); - - size_t j = 0; - while (j < c.size() && node_already_used(c[j].node)) { ++j; } - if (j != c.size()) { - moves_to_empty_blocks.push_back(c[j]); - is_empty.set(i, false); - } - - i = is_empty.find_next(i); - } - - } - return moves_to_empty_blocks; - } - - // explicitly instantiate so the compiler can generate them when compiling this cpp file - namespace { - #define REBALANCER(X, Y) Rebalancer - } - - // explicitly instantiate so the compiler can generate them when compiling this cpp file - INSTANTIATE_CLASS_WITH_TYPE_TRAITS_AND_GAIN_TYPES(REBALANCER) -} diff --git a/mt-kahypar/partition/refinement/rebalancing/rebalancer.h b/mt-kahypar/partition/refinement/rebalancing/rebalancer.h deleted file mode 100644 index 4f6c076ee..000000000 --- a/mt-kahypar/partition/refinement/rebalancing/rebalancer.h +++ /dev/null @@ -1,153 +0,0 @@ -/******************************************************************************* - * MIT License - * - * This file is part of Mt-KaHyPar. - * - * Copyright (C) 2020 Lars Gottesbüren - * Copyright (C) 2019 Tobias Heuer - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - ******************************************************************************/ - -#pragma once - -#include - -#include "mt-kahypar/partition/context.h" -#include "mt-kahypar/partition/metrics.h" -#include "mt-kahypar/partition/refinement/i_refiner.h" -#include "mt-kahypar/partition/refinement/gains/km1/km1_gain_computation.h" -#include "mt-kahypar/partition/refinement/gains/cut/cut_gain_computation.h" -#include "mt-kahypar/partition/refinement/gains/gain_cache_ptr.h" -#include "mt-kahypar/utils/cast.h" - -namespace mt_kahypar { -template -class Rebalancer final : public IRebalancer { - private: - using PartitionedHypergraph = typename TypeTraits::PartitionedHypergraph; - using GainCache = typename GainTypes::GainCache; - using GainCalculator = typename GainTypes::GainComputation; - using AtomicWeight = parallel::IntegralAtomicWrapper; - - static constexpr bool debug = false; - static constexpr bool enable_heavy_assert = false; - - static constexpr Gain MIN_PQ_GAIN_THRESHOLD = 5; - -public: - - struct MoveGainComparator { - bool operator()(const Move& lhs, const Move& rhs) { - return lhs.gain > rhs.gain || (lhs.gain == rhs.gain && lhs.node < rhs.node); - } - }; - - using MovePQ = std::priority_queue, MoveGainComparator>; - - struct IndexedMovePQ { - explicit IndexedMovePQ(const size_t idx) : - idx(idx), - pq() { } - - size_t idx; - MovePQ pq; - }; - - explicit Rebalancer(const Context& context) : - _context(context), - _current_k(context.partition.k), - _gain(context), - _part_weights(_context.partition.k) { } - - explicit Rebalancer(HypernodeID , const Context& context, GainCache&) : - Rebalancer(context) { } - - explicit Rebalancer(HypernodeID num_nodes, const Context& context, gain_cache_t gain_cache) : - Rebalancer(num_nodes, context, GainCachePtr::cast(gain_cache)) {} - - Rebalancer(const Rebalancer&) = delete; - Rebalancer(Rebalancer&&) = delete; - - Rebalancer & operator= (const Rebalancer &) = delete; - Rebalancer & operator= (Rebalancer &&) = delete; - - bool refineImpl(mt_kahypar_partitioned_hypergraph_t& hypergraph, - const vec&, - Metrics& best_metrics, - double) final ; - - void initializeImpl(mt_kahypar_partitioned_hypergraph_t&) final { } - - bool refineAndOutputMovesImpl(mt_kahypar_partitioned_hypergraph_t&, - const vec&, - vec>&, - Metrics&, - const double) override final { - ALWAYS_ASSERT(false, "not implemented"); - } - - vec repairEmptyBlocks(PartitionedHypergraph& phg); - -private: - - template - bool moveVertex(PartitionedHypergraph& phg, - const HypernodeID hn, - const Move& move, - const F& objective_delta) { - ASSERT(phg.partID(hn) == move.from); - const PartitionID from = move.from; - const PartitionID to = move.to; - const HypernodeWeight node_weight = phg.nodeWeight(hn); - if ( from != to ) { - // Before moving, we ensure that the block we move the vertex to does - // not become overloaded - _part_weights[to] += node_weight; - if ( _part_weights[to] <= _context.partition.max_part_weights[to] ) { - if ( phg.changeNodePart(hn, from, to, objective_delta) ) { - DBG << "Moved vertex" << hn << "from block" << from << "to block" << to - << "with gain" << move.gain; - _part_weights[from] -= node_weight; - return true; - } - } - _part_weights[to] -= node_weight; - } - return false; - } - - - void resizeDataStructuresForCurrentK() { - // If the number of blocks changes, we resize data structures - // (can happen during deep multilevel partitioning) - if ( _current_k != _context.partition.k ) { - _current_k = _context.partition.k; - _gain.changeNumberOfBlocks(_current_k); - _part_weights = parallel::scalable_vector(_context.partition.k); - } - } - - const Context& _context; - PartitionID _current_k; - GainCalculator _gain; - parallel::scalable_vector _part_weights; -}; - -} // namespace kahypar diff --git a/mt-kahypar/partition/registries/register_refinement_algorithms.cpp b/mt-kahypar/partition/registries/register_refinement_algorithms.cpp index 12a65a6bd..91a1f5218 100644 --- a/mt-kahypar/partition/registries/register_refinement_algorithms.cpp +++ b/mt-kahypar/partition/registries/register_refinement_algorithms.cpp @@ -181,12 +181,6 @@ REGISTER_DISPATCHED_FLOW_SCHEDULER(FlowAlgorithm::flow_cutter, context.partition.gain_policy)); REGISTER_FLOW_SCHEDULER(FlowAlgorithm::do_nothing, DoNothingRefiner, 4); -REGISTER_DISPATCHED_REBALANCER(RebalancingAlgorithm::simple_rebalancer, - RebalancerDispatcher, - kahypar::meta::PolicyRegistry::getInstance().getPolicy( - context.partition.partition_type), - kahypar::meta::PolicyRegistry::getInstance().getPolicy( - context.partition.gain_policy)); REGISTER_DISPATCHED_REBALANCER(RebalancingAlgorithm::rebalancer, RebalancerV2Dispatcher, kahypar::meta::PolicyRegistry::getInstance().getPolicy( diff --git a/tests/partition/refinement/CMakeLists.txt b/tests/partition/refinement/CMakeLists.txt index 4467f0901..7355e7af4 100644 --- a/tests/partition/refinement/CMakeLists.txt +++ b/tests/partition/refinement/CMakeLists.txt @@ -7,7 +7,6 @@ target_sources(mt_kahypar_tests PRIVATE gain_policy_test.cc label_propagation_refiner_test.cc rollback_test.cc - rebalance_test.cc twoway_fm_refiner_test.cc gain_test.cc gain_cache_test.cc diff --git a/tests/partition/refinement/rebalance_test.cc b/tests/partition/refinement/rebalance_test.cc deleted file mode 100644 index d168406fc..000000000 --- a/tests/partition/refinement/rebalance_test.cc +++ /dev/null @@ -1,123 +0,0 @@ -/******************************************************************************* - * MIT License - * - * This file is part of Mt-KaHyPar. - * - * Copyright (C) 2020 Lars Gottesbüren - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - ******************************************************************************/ - -#include -#include - - -#include "gmock/gmock.h" - -#include "mt-kahypar/definitions.h" -#include "mt-kahypar/io/hypergraph_factory.h" -#include "mt-kahypar/partition/refinement/rebalancing/rebalancer.h" -#include "mt-kahypar/partition/refinement/gains/gain_definitions.h" - -using ::testing::Test; - -namespace mt_kahypar { - -namespace { - using TypeTraits = StaticHypergraphTypeTraits; - using Hypergraph = typename TypeTraits::Hypergraph; - using PartitionedHypergraph = typename TypeTraits::PartitionedHypergraph; - using Km1Rebalancer = Rebalancer; -} - - -TEST(RebalanceTests, HeapSortWithMoveGainComparator) { - vec moves; - vec gains = { 52, 12, 72, -154, 2672, -717, 1346, -7111, -113461, 136682, 3833 }; - - for (HypernodeID i = 0; i < gains.size(); ++i) { - moves.push_back(Move{-1, -1 , i, gains[i]}); - } - - std::make_heap(moves.begin(), moves.end(), Km1Rebalancer::MoveGainComparator()); - for (size_t i = 0; i < moves.size(); ++i) { - std::pop_heap(moves.begin(), moves.end() - i, Km1Rebalancer::MoveGainComparator()); - } - - // assert that moves is sorted descendingly - ASSERT_TRUE(std::is_sorted(moves.begin(), moves.end(), - [](const Move& lhs, const Move& rhs) { - return lhs.gain > rhs.gain; - }) ); -} - -TEST(RebalanceTests, FindsMoves) { - PartitionID k = 8; - Context context; - context.partition.k = k; - context.partition.epsilon = 0.03; - Hypergraph hg = io::readInputFile( - "../tests/instances/contracted_ibm01.hgr", FileFormat::hMetis, - true /* enable stable construction */); - context.setupPartWeights(hg.totalWeight()); - PartitionedHypergraph phg = PartitionedHypergraph(k, hg); - - HypernodeID nodes_per_part = hg.initialNumNodes() / (k-4); - ASSERT(hg.initialNumNodes() % (k - 4) == 0); - for (PartitionID i = 0; i < k - 4; ++i) { - for (HypernodeID u = i * nodes_per_part; u < (i+1) * nodes_per_part; ++u) { - phg.setOnlyNodePart(u, i); - } - } - phg.initializePartition(); - Km1GainCache gain_cache; - gain_cache.initializeGainCache(phg); - - Km1Rebalancer rebalancer(context); - vec moves_to_empty_blocks = rebalancer.repairEmptyBlocks(phg); - - ASSERT_EQ(moves_to_empty_blocks.size(), 4); - - for (Move& m : moves_to_empty_blocks) { - ASSERT_EQ(gain_cache.gain(m.node, m.from, m.to), m.gain); - Gain recomputed_gain = gain_cache.recomputeBenefitTerm(phg, m.node, m.to) - - gain_cache.recomputePenaltyTerm(phg, m.node); - if (recomputed_gain == 0) { - ASSERT_TRUE([&]() { - for (HyperedgeID e : phg.incidentEdges(m.node)) { - if (phg.pinCountInPart(e, m.from) != 1) { - return false; - } - } - return true; - }()); - } - ASSERT_EQ(m.gain, recomputed_gain); - ASSERT_EQ(m.from, phg.partID(m.node)); - ASSERT_EQ(phg.partWeight(m.to), 0); - ASSERT_GE(m.to, k - 4); - ASSERT_LT(m.from, k - 4); - phg.changeNodePart(gain_cache, m.node, m.from, m.to); - } - - moves_to_empty_blocks = rebalancer.repairEmptyBlocks(phg); - ASSERT_EQ(moves_to_empty_blocks.size(), 0); -} - -} \ No newline at end of file