Skip to content

Commit bd9fec2

Browse files
authored
Merge branch 'main' into fix-current-sensor-output-residuals
2 parents 96dd6bb + 22d88bc commit bd9fec2

File tree

5 files changed

+102
-25
lines changed

5 files changed

+102
-25
lines changed

docs/user_manual/calculations.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -728,7 +728,7 @@ Power flow calculations that take the behavior of these regulators into account
728728

729729
We provide the control logic used for tap changing. For simplicity, we demonstrate the case where the regulator control side and the transformer tap side are at different sides.
730730

731-
- Regulated transformers are ranked according to how close they are to {hoverxreftooltip}`sources <user_manual/components:source>` in terms of the amount of regulated transformers inbetween.
731+
- Regulated transformers are ranked according to how close they are to {hoverxreftooltip}`sources <user_manual/components:source>` in terms of the amount of regulated transformers inbetween. In the presence of meshed grids, transformers with conflicting ranks will be ranked the last.
732732
- Transformers are regulated in order according to their ranks.
733733
- Initialize all transformers to their starting tap position (see {hoverxreftooltip}`user_manual/calculations:Initialization and exploitation of regulated transformers`)
734734
- Find the optimal state using the following procedure

power_grid_model_c/power_grid_model/include/power_grid_model/common/exception.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,8 @@ class IterationDiverge : public PowerGridError {
137137

138138
class MaxIterationReached : public IterationDiverge {
139139
public:
140-
MaxIterationReached(std::string_view msg = "") {
141-
append_msg(std::format("Maximum number of iterations reached{}\n", msg));
140+
MaxIterationReached(std::string const& msg = "") {
141+
append_msg(std::format("Maximum number of iterations reached {}\n", msg));
142142
}
143143
};
144144

power_grid_model_c/power_grid_model/include/power_grid_model/optimizer/tap_position_optimizer.hpp

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ using EdgeWeight = int64_t;
4141
using RankedTransformerGroups = std::vector<std::vector<Idx2D>>;
4242

4343
constexpr auto infty = std::numeric_limits<Idx>::max();
44-
constexpr Idx2D unregulated_idx = {.group = -1, .pos = -1};
44+
constexpr auto last_rank = infty - 1;
45+
constexpr Idx2D unregulated_idx = {-1, -1};
4546
struct TrafoGraphVertex {
4647
bool is_source{};
4748
};
@@ -260,11 +261,12 @@ inline auto build_transformer_graph(State const& state) -> TransformerGraph {
260261
return trafo_graph;
261262
}
262263

263-
inline void process_edges_dijkstra(Idx v, std::vector<EdgeWeight>& vertex_distances, TransformerGraph const& graph) {
264+
inline void process_edges_dijkstra(Idx search_start_vertex, std::vector<EdgeWeight>& vertex_distances,
265+
TransformerGraph const& graph) {
264266
using TrafoGraphElement = std::pair<EdgeWeight, TrafoGraphIdx>;
265267
std::priority_queue<TrafoGraphElement, std::vector<TrafoGraphElement>, std::greater<>> pq;
266-
vertex_distances[v] = 0;
267-
pq.emplace(0, v);
268+
vertex_distances[search_start_vertex] = 0;
269+
pq.emplace(0, search_start_vertex);
268270

269271
while (!pq.empty()) {
270272
auto [dist, u] = pq.top();
@@ -274,19 +276,14 @@ inline void process_edges_dijkstra(Idx v, std::vector<EdgeWeight>& vertex_distan
274276
continue;
275277
}
276278

277-
BGL_FORALL_EDGES(e, graph, TransformerGraph) {
279+
BGL_FORALL_OUTEDGES(u, e, graph, TransformerGraph) {
278280
auto s = boost::source(e, graph);
279281
auto t = boost::target(e, graph);
280282
const EdgeWeight weight = graph[e].weight;
281283

282-
// We can not use BGL_FORALL_OUTEDGES here because we need information
283-
// regardless of edge direction
284-
if (u == s && vertex_distances[s] + weight < vertex_distances[t]) {
284+
if (vertex_distances[s] + weight < vertex_distances[t]) {
285285
vertex_distances[t] = vertex_distances[s] + weight;
286286
pq.emplace(vertex_distances[t], t);
287-
} else if (u == t && vertex_distances[t] + weight < vertex_distances[s]) {
288-
vertex_distances[s] = vertex_distances[t] + weight;
289-
pq.emplace(vertex_distances[s], s);
290287
}
291288
}
292289
}
@@ -328,11 +325,16 @@ inline auto get_edge_weights(TransformerGraph const& graph) -> TrafoGraphEdgePro
328325
// situations can happen.
329326
// The logic still holds in meshed grids, albeit operating a more complex graph.
330327
if (!is_unreachable(edge_src_rank) || !is_unreachable(edge_tgt_rank)) {
331-
if (edge_src_rank != edge_tgt_rank - 1) {
332-
throw AutomaticTapInputError("The control side of a transformer regulator should be relatively further "
333-
"away from the source than the tap side.\n");
328+
if ((edge_src_rank == infty) || (edge_tgt_rank == infty)) {
329+
throw AutomaticTapInputError("The transformer is being controlled from non source side towards source "
330+
"side.\n");
331+
} else if (edge_src_rank != edge_tgt_rank - 1) {
332+
// Control side is also controlled by a closer regulated transformer.
333+
// Make this transformer have the lowest possible priority.
334+
result.emplace_back(graph[e].regulated_idx, last_rank);
335+
} else {
336+
result.emplace_back(graph[e].regulated_idx, edge_tgt_rank);
334337
}
335-
result.emplace_back(graph[e].regulated_idx, edge_tgt_rank);
336338
}
337339
}
338340

@@ -663,7 +665,7 @@ template <symmetry_tag sym> struct NodeState {
663665

664666
class RankIteration {
665667
public:
666-
RankIteration(std::vector<IntS> iterations_per_rank, Idx rank_index)
668+
RankIteration(std::vector<uint64_t> iterations_per_rank, Idx rank_index)
667669
: iterations_per_rank_{std::move(iterations_per_rank)}, rank_index_{rank_index} {}
668670

669671
// Getters
@@ -692,7 +694,7 @@ class RankIteration {
692694
};
693695

694696
private:
695-
std::vector<IntS> iterations_per_rank_;
697+
std::vector<uint64_t> iterations_per_rank_;
696698
Idx rank_index_{};
697699
};
698700

@@ -1001,7 +1003,7 @@ class TapPositionOptimizerImpl<std::tuple<TransformerTypes...>, StateCalculator,
10011003
strategy_ == OptimizerStrategy::global_maximum || strategy_ == OptimizerStrategy::local_maximum;
10021004
bool tap_changed = true;
10031005
Idx rank_index = 0;
1004-
RankIteration rank_iterator(std::vector<IntS>(regulator_order.size()), rank_index);
1006+
RankIteration rank_iterator(std::vector<uint64_t>(regulator_order.size()), rank_index);
10051007

10061008
while (tap_changed) {
10071009
tap_changed = false;
@@ -1021,8 +1023,10 @@ class TapPositionOptimizerImpl<std::tuple<TransformerTypes...>, StateCalculator,
10211023
rank_index = rank_iterator.rank_index();
10221024

10231025
if (tap_changed) {
1024-
if (static_cast<uint64_t>(iterations_per_rank[rank_index]) > 2 * max_tap_ranges_per_rank[rank_index]) {
1025-
throw MaxIterationReached{"TapPositionOptimizer::iterate"};
1026+
if (iterations_per_rank[rank_index] > 2 * max_tap_ranges_per_rank[rank_index]) {
1027+
throw MaxIterationReached{
1028+
std::format("TapPositionOptimizer::iterate {} iterations reached: {}x2 iterations in rank {}",
1029+
iterations_per_rank[rank_index], max_tap_ranges_per_rank[rank_index], rank_index)};
10261030
}
10271031
update_state(update_data);
10281032
result = calculate_(state, method);

tests/cpp_unit_tests/test_tap_position_optimizer.cpp

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,10 @@ TEST_CASE("Test Transformer ranking") {
422422

423423
SUBCASE("Ranking complete the graph") {
424424
// The test grid 1 is not compatible with the updated logic for step up transformers
425-
CHECK_THROWS_AS(pgm_tap::rank_transformers(state), AutomaticTapInputError);
425+
pgm_tap::RankedTransformerGroups order = pgm_tap::rank_transformers(state);
426+
pgm_tap::RankedTransformerGroups const ref_order{
427+
{{Idx2D{3, 0}, Idx2D{3, 1}, Idx2D{4, 0}}, {Idx2D{3, 2}}, {Idx2D{3, 3}, Idx2D{3, 4}}}};
428+
CHECK(order == ref_order);
426429
}
427430
}
428431

@@ -507,6 +510,62 @@ TEST_CASE("Test Transformer ranking") {
507510
{Idx2D{.group = 3, .pos = 2}, Idx2D{.group = 3, .pos = 3}, Idx2D{.group = 3, .pos = 5}}}};
508511
CHECK(order == ref_order);
509512
}
513+
514+
SUBCASE("Full grid 3 - For Meshed grid with low priority ranks") {
515+
// =====Test Grid=====
516+
// ________[0]________
517+
// | |
518+
// | |
519+
// | [1]
520+
// | |
521+
// _|______[2]_____|__
522+
// |
523+
// [3]
524+
TestState state;
525+
std::vector<NodeInput> nodes{{0, 10e3}, {1, 10e3}, {2, 10e3}, {3, 10e3}};
526+
main_core::add_component<Node>(state, nodes.begin(), nodes.end(), 50.0);
527+
528+
std::vector<TransformerInput> transformers{get_transformer(11, 0, 1, BranchSide::to),
529+
get_transformer(12, 1, 2, BranchSide::from),
530+
get_transformer(13, 2, 3, BranchSide::from)};
531+
main_core::add_component<Transformer>(state, transformers.begin(), transformers.end(), 50.0);
532+
533+
std::vector<LineInput> lines{get_line_input(21, 0, 2)};
534+
main_core::add_component<Line>(state, lines.begin(), lines.end(), 50.0);
535+
536+
std::vector<SourceInput> sources{{31, 0, 1, 1.0, 0, nan, nan, nan}};
537+
main_core::add_component<Source>(state, sources.begin(), sources.end(), 50.0);
538+
539+
std::vector<TransformerTapRegulatorInput> regulators{get_regulator(41, 11, ControlSide::to),
540+
get_regulator(42, 12, ControlSide::to),
541+
get_regulator(43, 13, ControlSide::to)};
542+
main_core::add_component<TransformerTapRegulator>(state, regulators.begin(), regulators.end(), 50.0);
543+
544+
state.components.set_construction_complete();
545+
546+
pgm_tap::RankedTransformerGroups order = pgm_tap::rank_transformers(state);
547+
pgm_tap::RankedTransformerGroups const ref_order{{{Idx2D{3, 0}}, {Idx2D{3, 2}}}, {{Idx2D{3, 1}}}};
548+
CHECK(order == ref_order);
549+
}
550+
551+
SUBCASE("Controlling from non source to source transformer") {
552+
TestState state;
553+
std::vector<NodeInput> nodes{{.id = 0, .u_rated = 150e3}, {.id = 1, .u_rated = 10e3}};
554+
main_core::add_component<Node>(state, nodes.begin(), nodes.end(), 50.0);
555+
556+
std::vector<TransformerInput> transformers{get_transformer(2, 0, 1, BranchSide::from)};
557+
main_core::add_component<Transformer>(state, transformers.begin(), transformers.end(), 50.0);
558+
559+
std::vector<SourceInput> sources{SourceInput{.id = 3, .node = 0, .status = IntS{1}, .u_ref = 1.0}};
560+
main_core::add_component<Source>(state, sources.begin(), sources.end(), 50.0);
561+
562+
std::vector<TransformerTapRegulatorInput> regulators{get_regulator(4, 2, ControlSide::from)};
563+
main_core::add_component<TransformerTapRegulator>(state, regulators.begin(), regulators.end(), 50.0);
564+
565+
state.components.set_construction_complete();
566+
567+
CHECK_THROWS_AS(pgm_tap::rank_transformers(state), AutomaticTapInputError);
568+
}
510569
}
511570

512571
namespace optimizer::tap_position_optimizer::test {
@@ -1603,7 +1662,7 @@ TEST_CASE("Test tap position optmizer I/O") {
16031662
TEST_CASE("Test RankIterator") {
16041663
std::vector<std::vector<IntS>> const regulator_order = {{0, 0, 0}, {0, 0, 0}};
16051664
bool tap_changed{false};
1606-
std::vector<IntS> iterations_per_rank = {2, 4, 6};
1665+
std::vector<uint64_t> iterations_per_rank = {2, 4, 6};
16071666
Idx rank_index{0};
16081667
bool update{false};
16091668
optimizer::tap_position_optimizer::RankIteration rank_iterator(iterations_per_rank, rank_index);

tests/unit/test_error_handling.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,10 +324,22 @@ def test_transformer_tap_regulator_control_side_not_closer_to_source():
324324
transformer_input["to_node"] = [1]
325325
transformer_input["from_status"] = [1]
326326
transformer_input["to_status"] = [1]
327+
transformer_input["u1"] = [1e4]
328+
transformer_input["u2"] = [4e2]
329+
transformer_input["sn"] = [1e5]
330+
transformer_input["uk"] = [0.1]
331+
transformer_input["pk"] = [1e3]
332+
transformer_input["i0"] = [1.0e-6]
333+
transformer_input["p0"] = [0.1]
327334
transformer_input["winding_from"] = [1]
328335
transformer_input["winding_to"] = [1]
329336
transformer_input["clock"] = [0]
330337
transformer_input["tap_side"] = [1]
338+
transformer_input["tap_pos"] = [0]
339+
transformer_input["tap_nom"] = [0]
340+
transformer_input["tap_min"] = [-1]
341+
transformer_input["tap_max"] = [1]
342+
transformer_input["tap_size"] = [100]
331343

332344
source_input = initialize_array("input", "source", 1)
333345
source_input["id"] = [3]
@@ -339,6 +351,8 @@ def test_transformer_tap_regulator_control_side_not_closer_to_source():
339351
transformer_tap_regulator_input["id"] = [4]
340352
transformer_tap_regulator_input["regulated_object"] = [2]
341353
transformer_tap_regulator_input["status"] = [1]
354+
transformer_tap_regulator_input["u_set"] = [10]
355+
transformer_tap_regulator_input["u_band"] = [200]
342356
transformer_tap_regulator_input["control_side"] = [0]
343357

344358
model = PowerGridModel(

0 commit comments

Comments
 (0)