Skip to content

Commit f869400

Browse files
Merge pull request #1140 from PowerGridModel/feature/counting-sort-swap-to-avoid-copying
Performance: Using swapping in places explicit copying could been avoided
2 parents a82a056 + 0b2a247 commit f869400

File tree

3 files changed

+146
-15
lines changed

3 files changed

+146
-15
lines changed

power_grid_model_c/power_grid_model/include/power_grid_model/index_mapping.hpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,12 @@ inline auto build_dense_mapping_comparison_sort(IdxVector const& idx_B_in_A, Idx
103103
DenseIndexMapping result;
104104
result.indvector.reserve(mapping_to_from.size());
105105
result.reorder.reserve(mapping_to_from.size());
106-
std::ranges::transform(mapping_to_from, std::back_inserter(result.indvector),
107-
[](DenseEntry const& to_from) { return to_from.first; });
108106

109-
std::ranges::transform(mapping_to_from, std::back_inserter(result.reorder),
110-
[](DenseEntry const& to_from) { return to_from.second; });
107+
// Use structured bindings to avoid copying from pairs
108+
for (auto [value, orig_idx] : mapping_to_from) {
109+
result.indvector.push_back(value);
110+
result.reorder.push_back(orig_idx);
111+
}
111112

112113
return result;
113114
}

power_grid_model_c/power_grid_model/include/power_grid_model/math_solver/y_bus.hpp

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,30 +40,38 @@ inline void append_element_vector(std::vector<YBusElementMap>& vec, Idx first_bu
4040

4141
// counting sort element
4242
inline void counting_sort_element(std::vector<YBusElementMap>& vec, Idx n_bus) {
43-
// count vec
44-
std::vector<YBusElementMap> count_vec(vec.size());
43+
// temp vec for swapping
44+
std::vector<YBusElementMap> temp_vec(vec.size());
4545
IdxVector counter(n_bus, 0);
46-
// sort column
46+
47+
// sort column first
4748
for (YBusElementMap const& element : vec) {
4849
++counter[element.pos.second];
4950
}
5051
for (size_t i = 1, n = counter.size(); i != n; ++i) {
5152
counter[i] += counter[i - 1];
5253
}
53-
for (auto it_element = vec.crbegin(); it_element != vec.crend(); ++it_element) {
54-
count_vec[--counter[it_element->pos.second]] = *it_element;
54+
for (auto it_element = vec.rbegin(); it_element != vec.rend(); ++it_element) {
55+
temp_vec[--counter[it_element->pos.second]] = std::move(*it_element);
5556
}
56-
// sort row
57+
58+
// swap vectors to avoid copying
59+
vec.swap(temp_vec);
60+
61+
// sort row second
5762
std::ranges::fill(counter, 0);
58-
for (YBusElementMap const& element : count_vec) {
63+
for (YBusElementMap const& element : vec) {
5964
++counter[element.pos.first];
6065
}
6166
for (size_t i = 1, n = counter.size(); i != n; ++i) {
6267
counter[i] += counter[i - 1];
6368
}
64-
for (auto it_element = count_vec.crbegin(); it_element != count_vec.crend(); ++it_element) {
65-
vec[--counter[it_element->pos.first]] = *it_element;
69+
for (auto it_element = vec.rbegin(); it_element != vec.rend(); ++it_element) {
70+
temp_vec[--counter[it_element->pos.first]] = std::move(*it_element);
6671
}
72+
73+
// final swap to get result back in vec
74+
vec.swap(temp_vec);
6775
}
6876

6977
// y bus structure

tests/cpp_unit_tests/test_y_bus.cpp

Lines changed: 124 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,129 @@ TEST_CASE("Incremental update y-bus") {
426426
}
427427
}
428428

429-
// TODO:
430-
// - test counting_sort_element()
429+
TEST_CASE("Test counting_sort_element") {
430+
using math_solver::counting_sort_element;
431+
using math_solver::YBusElementMap;
432+
using enum power_grid_model::YBusElementType;
433+
434+
SUBCASE("Test basic sorting") {
435+
// Create test data: elements at various matrix positions
436+
std::vector<YBusElementMap> vec = {
437+
{{2, 1}, {bft, 5}}, // pos (2,1)
438+
{{0, 0}, {bff, 0}}, // pos (0,0)
439+
{{1, 2}, {btf, 3}}, // pos (1,2)
440+
{{0, 1}, {bft, 1}}, // pos (0,1)
441+
{{2, 1}, {shunt, 6}}, // pos (2,1) - same position as first
442+
{{1, 0}, {btf, 2}}, // pos (1,0)
443+
{{2, 2}, {btt, 7}}, // pos (2,2)
444+
};
445+
446+
// Expected sorted order: by row first, then by column
447+
// (0,0), (0,1), (1,0), (1,2), (2,1), (2,1), (2,2)
448+
std::vector<std::pair<Idx, Idx>> expected_positions = {{0, 0}, {0, 1}, {1, 0}, {1, 2}, {2, 1}, {2, 1}, {2, 2}};
449+
450+
Idx const n_bus = 3;
451+
counting_sort_element(vec, n_bus);
452+
453+
// Verify sorting
454+
CHECK(vec.size() == 7);
455+
for (size_t i = 0; i < vec.size(); ++i) {
456+
CHECK(vec[i].pos.first == expected_positions[i].first);
457+
CHECK(vec[i].pos.second == expected_positions[i].second);
458+
}
459+
460+
// Verify specific elements are preserved correctly
461+
CHECK(vec[0].element.element_type == bff);
462+
CHECK(vec[0].element.idx == 0);
463+
CHECK(vec[1].element.element_type == bft);
464+
CHECK(vec[1].element.idx == 1);
465+
}
466+
467+
SUBCASE("Test with single bus") {
468+
std::vector<YBusElementMap> vec = {{{0, 0}, {shunt, 10}}};
469+
470+
counting_sort_element(vec, 1);
471+
472+
CHECK(vec.size() == 1);
473+
CHECK(vec[0].pos == std::make_pair(0, 0));
474+
CHECK(vec[0].element.element_type == shunt);
475+
CHECK(vec[0].element.idx == 10);
476+
}
477+
478+
SUBCASE("Test with empty vector") {
479+
std::vector<YBusElementMap> vec;
480+
counting_sort_element(vec, 5);
481+
CHECK(vec.empty());
482+
}
483+
484+
SUBCASE("Test stability - elements with same position maintain relative order") {
485+
std::vector<YBusElementMap> vec = {
486+
{{1, 1}, {bff, 100}},
487+
{{1, 1}, {bft, 200}},
488+
{{1, 1}, {shunt, 300}},
489+
};
490+
491+
counting_sort_element(vec, 2);
492+
493+
CHECK(vec.size() == 3);
494+
// All should be at position (1,1)
495+
for (const auto& element : vec) {
496+
CHECK(element.pos == std::make_pair(1, 1));
497+
}
498+
// Original relative order should be preserved (stable sort)
499+
CHECK(vec[0].element.idx == 100);
500+
CHECK(vec[1].element.idx == 200);
501+
CHECK(vec[2].element.idx == 300);
502+
}
503+
504+
SUBCASE("Test large sparse matrix scenario") {
505+
std::vector<YBusElementMap> vec;
506+
Idx const n_bus = 10;
507+
508+
// Add elements in reverse order to test sorting thoroughly
509+
for (Idx i = n_bus * n_bus - 1; i != static_cast<Idx>(-1); --i) {
510+
Idx const row = i / n_bus;
511+
Idx const col = i % n_bus;
512+
if ((row + col) % 3 == 0) { // Sparse pattern
513+
vec.push_back({{row, col}, {bff, row * n_bus + col}});
514+
}
515+
}
516+
517+
size_t original_size = vec.size();
518+
counting_sort_element(vec, n_bus);
519+
520+
CHECK(vec.size() == original_size);
521+
522+
// Verify sorted order
523+
for (size_t i = 1; i < vec.size(); ++i) {
524+
auto [prev_row, prev_col] = vec[i - 1].pos;
525+
auto [curr_row, curr_col] = vec[i].pos;
526+
527+
// Check row-major order
528+
if (prev_row == curr_row) {
529+
CHECK(prev_col <= curr_col); // Same row, column should be non-decreasing
530+
} else {
531+
CHECK(prev_row < curr_row); // Different row, previous row should be smaller
532+
}
533+
}
534+
}
535+
536+
SUBCASE("Test all YBusElementType values") {
537+
std::vector<YBusElementMap> vec = {
538+
{{1, 1}, {fill_in_tf, 6}}, {{0, 1}, {bft, 1}}, {{1, 0}, {btf, 2}}, {{0, 0}, {bff, 0}},
539+
{{1, 1}, {btt, 3}}, {{2, 2}, {shunt, 4}}, {{1, 2}, {fill_in_ft, 5}},
540+
};
541+
542+
counting_sort_element(vec, 3);
543+
544+
// Verify positions are sorted correctly
545+
std::vector<std::pair<Idx, Idx>> expected_positions = {{0, 0}, {0, 1}, {1, 0}, {1, 1}, {1, 1}, {1, 2}, {2, 2}};
546+
547+
CHECK(vec.size() == 7);
548+
for (size_t i = 0; i < vec.size(); ++i) {
549+
CHECK(vec[i].pos == expected_positions[i]);
550+
}
551+
}
552+
}
431553

432554
} // namespace power_grid_model

0 commit comments

Comments
 (0)