Skip to content

Commit 09ee5a6

Browse files
Merge pull request #562 from PowerGridModel/feature/transformer-tap-ranking-part-1
Transformer ranking part 1
2 parents 8352201 + 1b5b839 commit 09ee5a6

File tree

10 files changed

+527
-100
lines changed

10 files changed

+527
-100
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,14 @@ class InvalidRegulatedObject : public PowerGridError {
118118
}
119119
};
120120

121+
class AutomaticTapCalculationError : public PowerGridError {
122+
public:
123+
AutomaticTapCalculationError(ID id) {
124+
append_msg("Automatic tap changing regulator with tap_side at LV side is not supported. Found at id" +
125+
std::to_string(id)); // NOSONAR
126+
}
127+
};
128+
121129
class IDWrongType : public PowerGridError {
122130
public:
123131
explicit IDWrongType(ID id) { append_msg("Wrong type for object with id " + std::to_string(id) + '\n'); }

power_grid_model_c/power_grid_model/include/power_grid_model/container.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,9 @@ class Container<RetrievableTypes<GettableTypes...>, StorageableTypes...> {
137137
}
138138

139139
// get size
140-
template <class Gettable> Idx size() const {
140+
template <class Gettable>
141+
requires(std::same_as<Gettable, GettableTypes> || ...)
142+
Idx size() const {
141143
assert(construction_complete_);
142144
return size_[get_cls_pos_v<Gettable, GettableTypes...>];
143145
}

power_grid_model_c/power_grid_model/include/power_grid_model/main_core/input.hpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111

1212
namespace power_grid_model::main_core {
1313

14+
constexpr std::array<Branch3Side, 3> const branch3_sides = {Branch3Side::side_1, Branch3Side::side_2,
15+
Branch3Side::side_3};
16+
1417
// template to construct components
1518
// using forward interators
1619
// different selection based on component type
@@ -124,6 +127,31 @@ inline void add_component(MainModelState<ComponentContainer>& state, ForwardIter
124127
}
125128
}();
126129

130+
if (regulated_object_idx.group == get_component_type_index<Transformer>(state)) {
131+
auto const& regulated_object = get_component<Transformer>(state, regulated_object_idx);
132+
133+
auto const non_tap_side =
134+
regulated_object.tap_side() == BranchSide::from ? BranchSide::to : BranchSide::from;
135+
if (get_component<Node>(state, regulated_object.node(regulated_object.tap_side())).u_rated() <
136+
get_component<Node>(state, regulated_object.node(non_tap_side)).u_rated()) {
137+
throw AutomaticTapCalculationError(id);
138+
}
139+
} else if (regulated_object_idx.group == get_component_type_index<ThreeWindingTransformer>(state)) {
140+
auto const& regulated_object = get_component<ThreeWindingTransformer>(state, regulated_object_idx);
141+
auto const tap_side_u_rated =
142+
get_component<Node>(state, regulated_object.node(regulated_object.tap_side())).u_rated();
143+
for (auto const side : branch3_sides) {
144+
if (side == regulated_object.tap_side()) {
145+
continue;
146+
}
147+
if (tap_side_u_rated < get_component<Node>(state, regulated_object.node(side)).u_rated()) {
148+
throw AutomaticTapCalculationError(id);
149+
}
150+
}
151+
} else {
152+
throw InvalidRegulatedObject(input.regulated_object, Component::name);
153+
}
154+
127155
auto const regulated_object_type = get_component<Base>(state, regulated_object_idx).math_model_type();
128156
double const u_rated = get_component<Node>(state, regulated_terminal).u_rated();
129157

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

Lines changed: 191 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@
66

77
#include "base_optimizer.hpp"
88

9+
#include "../all_components.hpp"
910
#include "../auxiliary/dataset.hpp"
1011
#include "../common/enum.hpp"
12+
#include "../common/exception.hpp"
13+
#include "../main_core/state_queries.hpp"
1114

1215
#include <boost/graph/compressed_sparse_row_graph.hpp>
16+
1317
#include <functional>
1418
#include <queue>
1519
#include <vector>
@@ -21,114 +25,239 @@ namespace detail = power_grid_model::optimizer::detail;
2125

2226
using TrafoGraphIdx = Idx;
2327
using EdgeWeight = int64_t;
24-
using WeightedTrafo = std::pair<Idx2D, EdgeWeight>;
25-
using WeightedTrafoList = std::vector<WeightedTrafo>;
26-
using RankedTransformerGroups = std::vector<std::vector<Idx2D>>;
2728
constexpr auto infty = std::numeric_limits<Idx>::max();
29+
constexpr Idx2D unregulated_idx = {-1, -1};
2830

2931
struct TrafoGraphVertex {
30-
bool is_source{}; // is_source = true if the vertex is a source
32+
bool is_source{};
3133
};
3234

3335
struct TrafoGraphEdge {
34-
Idx2D pos{};
36+
Idx2D regulated_idx{};
3537
EdgeWeight weight{};
38+
39+
bool operator==(const TrafoGraphEdge& other) const {
40+
return regulated_idx == other.regulated_idx && weight == other.weight;
41+
} // thanks boost
42+
43+
auto operator<=>(const TrafoGraphEdge& other) const {
44+
if (auto cmp = weight <=> other.weight; cmp != 0) { // NOLINT(modernize-use-nullptr)
45+
return cmp;
46+
}
47+
if (auto cmp = regulated_idx.group <=> other.regulated_idx.group; cmp != 0) { // NOLINT(modernize-use-nullptr)
48+
return cmp;
49+
}
50+
return regulated_idx.pos <=> other.regulated_idx.pos;
51+
}
52+
};
53+
54+
using TrafoGraphEdges = std::vector<std::pair<TrafoGraphIdx, TrafoGraphIdx>>;
55+
using TrafoGraphEdgeProperties = std::vector<TrafoGraphEdge>;
56+
using RankedTransformerGroups = std::vector<Idx2D>;
57+
58+
struct RegulatedObjects {
59+
std::set<Idx> transformers{};
60+
std::set<Idx> transformers3w{};
3661
};
3762

38-
// TODO(mgovers): investigate whether this really is the correct graph structure
3963
using TransformerGraph = boost::compressed_sparse_row_graph<boost::directedS, TrafoGraphVertex, TrafoGraphEdge,
4064
boost::no_property, TrafoGraphIdx, TrafoGraphIdx>;
4165

66+
inline void add_to_edge(TrafoGraphEdges& edges, TrafoGraphEdgeProperties& edge_props, Idx const& start, Idx const& end,
67+
TrafoGraphEdge const& edge_prop) {
68+
edges.emplace_back(start, end);
69+
edge_props.emplace_back(edge_prop);
70+
}
71+
72+
inline void process_trafo3w_edge(ThreeWindingTransformer const& transformer3w, bool const& trafo3w_is_regulated,
73+
Idx2D const& trafo3w_idx, TrafoGraphEdges& edges,
74+
TrafoGraphEdgeProperties& edge_props) {
75+
using enum Branch3Side;
76+
77+
constexpr std::array<std::tuple<Branch3Side, Branch3Side>, 3> const branch3_combinations{
78+
{{side_1, side_2}, {side_2, side_3}, {side_3, side_1}}};
79+
80+
for (auto const& [first_side, second_side] : branch3_combinations) {
81+
if (!transformer3w.status(first_side) || !transformer3w.status(second_side)) {
82+
continue;
83+
}
84+
auto const& from_node = transformer3w.node(first_side);
85+
auto const& to_node = transformer3w.node(second_side);
86+
87+
auto const tap_at_first_side = transformer3w.tap_side() == first_side;
88+
auto const single_direction_condition =
89+
trafo3w_is_regulated && (tap_at_first_side || transformer3w.tap_side() == second_side);
90+
// ranking
91+
if (single_direction_condition) {
92+
auto const& tap_side_node = tap_at_first_side ? from_node : to_node;
93+
auto const& non_tap_side_node = tap_at_first_side ? to_node : from_node;
94+
// add regulated idx only when the first side node is tap side node.
95+
// This is done to add only one directional edge with regulated idx.
96+
Idx2D const regulated_idx = from_node == tap_side_node ? unregulated_idx : trafo3w_idx;
97+
add_to_edge(edges, edge_props, tap_side_node, non_tap_side_node, {regulated_idx, 1});
98+
} else {
99+
add_to_edge(edges, edge_props, from_node, to_node, {unregulated_idx, 1});
100+
add_to_edge(edges, edge_props, to_node, from_node, {unregulated_idx, 1});
101+
}
102+
}
103+
}
104+
105+
template <std::derived_from<ThreeWindingTransformer> Component, class ComponentContainer>
106+
requires main_core::model_component_state_c<main_core::MainModelState, ComponentContainer, Component>
107+
constexpr void add_edge(main_core::MainModelState<ComponentContainer> const& state,
108+
RegulatedObjects const& regulated_objects, TrafoGraphEdges& edges,
109+
TrafoGraphEdgeProperties& edge_props) {
110+
111+
for (auto const& transformer3w : state.components.template citer<ThreeWindingTransformer>()) {
112+
bool const trafo3w_is_regulated = regulated_objects.transformers3w.contains(transformer3w.id());
113+
Idx2D const trafo3w_idx = main_core::get_component_idx_by_id(state, transformer3w.id());
114+
process_trafo3w_edge(transformer3w, trafo3w_is_regulated, trafo3w_idx, edges, edge_props);
115+
}
116+
}
117+
118+
template <std::derived_from<Transformer> Component, class ComponentContainer>
119+
requires main_core::model_component_state_c<main_core::MainModelState, ComponentContainer, Component>
120+
constexpr void add_edge(main_core::MainModelState<ComponentContainer> const& state,
121+
RegulatedObjects const& regulated_objects, TrafoGraphEdges& edges,
122+
TrafoGraphEdgeProperties& edge_props) {
123+
for (auto const& transformer : state.components.template citer<Transformer>()) {
124+
if (!transformer.from_status() || !transformer.to_status()) {
125+
continue;
126+
}
127+
auto const& from_node = transformer.from_node();
128+
auto const& to_node = transformer.to_node();
129+
130+
if (regulated_objects.transformers.contains(transformer.id())) {
131+
auto const tap_at_from_side = transformer.tap_side() == BranchSide::from;
132+
auto const& tap_side_node = tap_at_from_side ? from_node : to_node;
133+
auto const& non_tap_side_node = tap_at_from_side ? to_node : from_node;
134+
add_to_edge(edges, edge_props, tap_side_node, non_tap_side_node,
135+
{main_core::get_component_idx_by_id(state, transformer.id()), 1});
136+
} else {
137+
add_to_edge(edges, edge_props, from_node, to_node, {unregulated_idx, 1});
138+
add_to_edge(edges, edge_props, to_node, from_node, {unregulated_idx, 1});
139+
}
140+
}
141+
}
142+
143+
template <std::derived_from<Branch> Component, class ComponentContainer>
144+
requires main_core::model_component_state_c<main_core::MainModelState, ComponentContainer, Component> &&
145+
(!transformer_c<Component>)
146+
constexpr void add_edge(main_core::MainModelState<ComponentContainer> const& state,
147+
RegulatedObjects const& /* regulated_objects */, TrafoGraphEdges& edges,
148+
TrafoGraphEdgeProperties& edge_props) {
149+
auto const& iter = state.components.template citer<Component>();
150+
edges.reserve(std::distance(iter.begin(), iter.end()) * 2);
151+
edge_props.reserve(std::distance(iter.begin(), iter.end()) * 2);
152+
for (auto const& branch : iter) {
153+
if (!branch.from_status() || !branch.to_status()) {
154+
continue;
155+
}
156+
add_to_edge(edges, edge_props, branch.from_node(), branch.to_node(), {unregulated_idx, 0});
157+
add_to_edge(edges, edge_props, branch.to_node(), branch.from_node(), {unregulated_idx, 0});
158+
}
159+
}
160+
161+
template <typename... ComponentTypes, main_core::main_model_state_c State>
162+
inline auto add_edges(State const& state, RegulatedObjects const& regulated_objects, TrafoGraphEdges& edges,
163+
TrafoGraphEdgeProperties& edge_props) {
164+
(add_edge<ComponentTypes>(state, regulated_objects, edges, edge_props), ...);
165+
}
166+
167+
template <main_core::main_model_state_c State>
168+
inline auto retrieve_regulator_info(State const& state) -> RegulatedObjects {
169+
RegulatedObjects regulated_objects;
170+
for (auto const& regulator : state.components.template citer<TransformerTapRegulator>()) {
171+
if (!regulator.status()) {
172+
continue;
173+
}
174+
if (regulator.regulated_object_type() == ComponentType::branch) {
175+
regulated_objects.transformers.emplace(regulator.regulated_object());
176+
} else {
177+
regulated_objects.transformers3w.emplace(regulator.regulated_object());
178+
}
179+
}
180+
return regulated_objects;
181+
}
182+
42183
template <main_core::main_model_state_c State>
43-
inline auto build_transformer_graph(State const& /*state*/) -> TransformerGraph {
44-
// TODO(nbharambe): implement
45-
return {};
184+
inline auto build_transformer_graph(State const& state) -> TransformerGraph {
185+
TrafoGraphEdges edges;
186+
TrafoGraphEdgeProperties edge_props;
187+
188+
const RegulatedObjects regulated_objects = retrieve_regulator_info(state);
189+
190+
add_edges<Transformer, ThreeWindingTransformer, Line, Link>(state, regulated_objects, edges, edge_props);
191+
192+
// build graph
193+
TransformerGraph trafo_graph{boost::edges_are_unsorted_multi_pass, edges.cbegin(), edges.cend(),
194+
edge_props.cbegin(),
195+
static_cast<TrafoGraphIdx>(state.components.template size<Node>())};
196+
197+
BGL_FORALL_VERTICES(v, trafo_graph, TransformerGraph) { trafo_graph[v].is_source = false; }
198+
199+
// Mark sources
200+
for (auto const& source : state.components.template citer<Source>()) {
201+
// ignore disabled sources
202+
trafo_graph[source.node()].is_source = source.status();
203+
}
204+
205+
return trafo_graph;
46206
}
47207

48-
inline auto process_edges_dijkstra(Idx v, std::vector<EdgeWeight>& edge_weight, std::vector<Idx2D>& edge_pos,
49-
TransformerGraph const& graph) -> void {
208+
inline void process_edges_dijkstra(Idx v, std::vector<EdgeWeight>& vertex_distances, TransformerGraph const& graph) {
50209
using TrafoGraphElement = std::pair<EdgeWeight, TrafoGraphIdx>;
51210
std::priority_queue<TrafoGraphElement, std::vector<TrafoGraphElement>, std::greater<>> pq;
52-
edge_weight[v] = 0;
53-
edge_pos[v] = {v, v};
211+
vertex_distances[v] = 0;
54212
pq.push({0, v});
55213

56214
while (!pq.empty()) {
57215
auto [dist, u] = pq.top();
58216
pq.pop();
59217

60-
if (dist != edge_weight[u]) {
218+
if (dist != vertex_distances[u]) {
61219
continue;
62220
}
63221

64-
for (auto e : boost::make_iterator_range(boost::out_edges(u, graph))) {
222+
BGL_FORALL_OUTEDGES(u, e, graph, TransformerGraph) {
65223
auto v = boost::target(e, graph);
66224
const EdgeWeight weight = graph[e].weight;
67225

68-
if (edge_weight[u] + weight < edge_weight[v]) {
69-
edge_weight[v] = edge_weight[u] + weight;
70-
edge_pos[v] = graph[e].pos;
71-
pq.push({edge_weight[v], v});
226+
if (vertex_distances[u] + weight < vertex_distances[v]) {
227+
vertex_distances[v] = vertex_distances[u] + weight;
228+
pq.push({vertex_distances[v], v});
72229
}
73230
}
74231
}
75232
}
76233

77-
// Step 2: Initialize the rank of all vertices (transformer nodes) as infinite (INT_MAX)
78-
// Step 3: Loop all the connected sources (status == 1)
79-
// a. Perform Dijkstra shortest path algorithm from the vertex with that source.
80-
// This is to determine the shortest path of all vertices to this particular source.
81-
inline auto get_edge_weights(TransformerGraph const& graph) -> WeightedTrafoList {
82-
std::vector<EdgeWeight> edge_weight(boost::num_vertices(graph), infty);
83-
std::vector<Idx2D> edge_pos(boost::num_vertices(graph));
84-
85-
for (auto v : boost::make_iterator_range(boost::vertices(graph))) {
234+
inline auto get_edge_weights(TransformerGraph const& graph) -> TrafoGraphEdgeProperties {
235+
std::vector<EdgeWeight> vertex_distances(boost::num_vertices(graph), infty);
236+
BGL_FORALL_VERTICES(v, graph, TransformerGraph) {
86237
if (graph[v].is_source) {
87-
process_edges_dijkstra(v, edge_weight, edge_pos, graph);
238+
process_edges_dijkstra(v, vertex_distances, graph);
88239
}
89240
}
90241

91-
WeightedTrafoList result;
92-
for (size_t i = 0; i < edge_weight.size(); ++i) {
93-
result.emplace_back(edge_pos[i], edge_weight[i]);
242+
TrafoGraphEdgeProperties result;
243+
BGL_FORALL_EDGES(e, graph, TransformerGraph) {
244+
if (graph[e].regulated_idx == unregulated_idx) {
245+
continue;
246+
}
247+
result.push_back({graph[e].regulated_idx, vertex_distances[boost::source(e, graph)]});
94248
}
95249

96250
return result;
97251
}
98252

99-
// Step 4: Loop all transformers with automatic tap changers, including the transformers which are not
100-
// fully connected
101-
// a.Rank of the transformer <-
102-
// i. Infinity(INT_MAX), if tap side of the transformer is disconnected.
103-
// The transformer regulation should be ignored
104-
// ii.Rank of the vertex at the tap side of the transformer, if tap side of the transformer is connected
105-
inline auto transformer_disconnected(Idx2D const& /*pos*/) -> bool {
106-
// <TODO: jguo> waiting for the functionalities in step 1 to be implemented
107-
return false;
108-
}
109-
110-
inline auto rank_transformers(WeightedTrafoList const& w_trafo_list) -> RankedTransformerGroups {
253+
inline auto rank_transformers(TrafoGraphEdgeProperties const& w_trafo_list) -> RankedTransformerGroups {
111254
auto sorted_trafos = w_trafo_list;
112255

113-
for (auto& trafo : sorted_trafos) {
114-
if (transformer_disconnected(trafo.first)) {
115-
trafo.second = infty;
116-
}
117-
}
118-
119256
std::sort(sorted_trafos.begin(), sorted_trafos.end(),
120-
[](const WeightedTrafo& a, const WeightedTrafo& b) { return a.second < b.second; });
121-
122-
RankedTransformerGroups groups;
123-
Idx last_weight = -1;
124-
for (const auto& trafo : sorted_trafos) {
125-
if (groups.empty() || last_weight != trafo.second) {
126-
groups.push_back(std::vector<Idx2D>{trafo.first});
127-
last_weight = trafo.second;
128-
} else {
129-
groups.back().push_back(trafo.first);
130-
}
131-
}
257+
[](const TrafoGraphEdge& a, const TrafoGraphEdge& b) { return a.weight < b.weight; });
258+
259+
RankedTransformerGroups groups(sorted_trafos.size());
260+
std::ranges::transform(sorted_trafos, groups.begin(), [](const TrafoGraphEdge& x) { return x.regulated_idx; });
132261
return groups;
133262
}
134263

0 commit comments

Comments
 (0)