Skip to content

Commit b1c0143

Browse files
committed
empty blocks: use removed degree zero nodes correctly (#225)
- allow empty blocks if it is known they can be fixed using degree zero nodes in postprocessing - prioritize fixing empty blocks in preprocessing - correctly handle individual part weights edge case: a removed node might be heavier than a block with very small weight
1 parent 725b606 commit b1c0143

3 files changed

Lines changed: 42 additions & 16 deletions

File tree

mt-kahypar/partition/metrics.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ BalanceMetrics imbalance_impl(const PartitionedHypergraph& hypergraph,
207207
ASSERT(context.partition.perfect_balance_part_weights.size() == (size_t)context.partition.k);
208208

209209
size_t num_empty_parts = 0;
210+
HypernodeWeight min_empty_part_weight = std::numeric_limits<HypernodeWeight>::max();
210211
double max_balance = 0.0;
211212
bool violates_balance = false;
212213
for (PartitionID i = 0; i < context.partition.k; ++i) {
@@ -219,11 +220,16 @@ BalanceMetrics imbalance_impl(const PartitionedHypergraph& hypergraph,
219220
}
220221
if (part_weight == 0) {
221222
num_empty_parts++;
223+
min_empty_part_weight = std::min(min_empty_part_weight, part_weight);
222224
}
223225
}
226+
224227
bool too_many_empty_parts = num_empty_parts > hypergraph.numRemovedHypernodes();
228+
// use conservative estimate
229+
bool too_small_empty_parts = num_empty_parts > 0
230+
&& min_empty_part_weight < hypergraph.maxWeightOfRemovedDegreeZeroNode();
225231
return BalanceMetrics{max_balance - 1.0, violates_balance,
226-
!context.partition.allow_empty_blocks && too_many_empty_parts};
232+
!context.partition.allow_empty_blocks && (too_many_empty_parts || too_small_empty_parts)};
227233
}
228234

229235
template<typename PartitionedHypergraph>

mt-kahypar/partition/preprocessing/sparsification/degree_zero_hn_remover.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,12 @@ class DegreeZeroHypernodeRemover {
7979
});
8080
// Sort blocks of partition in increasing order of their weight
8181
auto distance_to_max = [&](const PartitionID block) {
82-
return hypergraph.partWeight(block) - _context.partition.max_part_weights[block];
82+
if (!_context.partition.allow_empty_blocks && hypergraph.partWeight(block) == 0) {
83+
// repairing empty blocks gets highest priority
84+
return -(_context.partition.max_part_weights[block] + hypergraph.totalWeight());
85+
} else {
86+
return hypergraph.partWeight(block) - _context.partition.max_part_weights[block];
87+
}
8388
};
8489
parallel::scalable_vector<PartitionID> blocks(_context.partition.k, 0);
8590
std::iota(blocks.begin(), blocks.end(), 0);

mt-kahypar/partition/refinement/rebalancing/repair_empty_blocks.cpp

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,35 @@ void RepairEmtpyBlocks<GraphAndGainTypes>::computeEmptyParts(PartitionedHypergra
6262
_empty_parts.push_back(block);
6363
}
6464
}
65+
66+
const auto& max_weights = _context.partition.max_part_weights;
67+
if (!_empty_parts.empty() && _context.partition.use_individual_part_weights) {
68+
// We sort the empty parts, so that
69+
// (1) we can determine which parts to skip for degree zero nodes
70+
// (2) computeBestMovesBlockIndependent can maintain the invariant that the last entry
71+
// of best_move_for_part is the worst move in the list. This invariant could be broken
72+
// if a node does not fit in a block (unlikely, but possible) and therefore "skips"
73+
// a part of the list. However, by sorting descending by part weight, this can't happen
74+
// since a move at position i + 1 is guaranteed to also fit in position i, and thus
75+
// can not be better than the move at position i (or it would be placed there).
76+
std::sort(_empty_parts.begin(), _empty_parts.end(), [&](PartitionID lhs, PartitionID rhs) {
77+
// deterministic tie breaking
78+
return max_weights[lhs] > max_weights[rhs] || (max_weights[lhs] == max_weights[rhs] && lhs < rhs);
79+
});
80+
}
81+
82+
if (!_empty_parts.empty() && phg.numRemovedHypernodes() > 0) {
83+
// since some of the blocks can be filled by degree zero nodes at the end, we can throw
84+
// out all blocks that can be filled in this way
85+
HypernodeWeight max_removed_weight = phg.maxWeightOfRemovedDegreeZeroNode();
86+
size_t last_fitting = 0;
87+
while (last_fitting < _empty_parts.size()
88+
&& max_removed_weight <= max_weights[_empty_parts[last_fitting]]) {
89+
++last_fitting;
90+
}
91+
size_t start = (phg.numRemovedHypernodes() >= last_fitting) ? 0 : last_fitting - phg.numRemovedHypernodes();
92+
_empty_parts.erase(_empty_parts.begin() + start, _empty_parts.begin() + last_fitting);
93+
}
6594
}
6695

6796
template <typename GraphAndGainTypes>
@@ -74,20 +103,6 @@ void RepairEmtpyBlocks<GraphAndGainTypes>::computeBestMovesBlockIndependent(Part
74103
_global_best_move_for_part.clear();
75104
_global_best_move_for_part.resize(_empty_parts.size(), Move{});
76105

77-
if (_context.partition.use_individual_part_weights) {
78-
// We sort the empty parts, so that we can maintain the invariant that the last entry
79-
// of best_move_for_part is the worst move in the list. This invariant could be broken
80-
// if a node does not fit in a block (unlikely, but possible) and therefore "skips"
81-
// a part of the list. However, by sorting descending by part weight, this can't happen
82-
// since a move at position i + 1 is guaranteed to also fit in position i, and thus
83-
// can not be better than the move at position i (or it would be placed there).
84-
const auto& max_weights = _context.partition.max_part_weights;
85-
std::sort(_empty_parts.begin(), _empty_parts.end(), [&](PartitionID lhs, PartitionID rhs) {
86-
// deterministic tie breaking
87-
return max_weights[lhs] > max_weights[rhs] || (max_weights[lhs] == max_weights[rhs] && lhs < rhs);
88-
});
89-
}
90-
91106
auto insert_move_into_list = [&](Move& current_move, vec<Move>& move_for_part) {
92107
const Move& worst_move = move_for_part.back();
93108
if (moveIsBetter(current_move, worst_move)) {

0 commit comments

Comments
 (0)