Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Step-up transformer tap changer support: feature branch #826

Draft
wants to merge 43 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
4bfc775
init
Jerry-Jinfeng-Guo Aug 20, 2024
db8e33f
updated transformer rank algorithm; updated tests; updated doc
Jerry-Jinfeng-Guo Aug 20, 2024
009771f
Merge branch 'main' into feature/updated-transformer-ranking
Jerry-Jinfeng-Guo Sep 24, 2024
2719982
Merge branch 'main' into feature/updated-transformer-ranking
Jerry-Jinfeng-Guo Oct 9, 2024
61cae1a
Merge branch 'main' into feature/updated-transformer-ranking
Jerry-Jinfeng-Guo Oct 21, 2024
1ed3cb1
Merge branch 'main' into feature/updated-transformer-ranking
Jerry-Jinfeng-Guo Nov 7, 2024
a4ad95c
Merge branch 'main' into feature/updated-transformer-ranking
Jerry-Jinfeng-Guo Nov 7, 2024
9b9f40f
base branch for step up transformer tap changer support
Jerry-Jinfeng-Guo Nov 11, 2024
e420373
Merge branch 'main' into feature/step-up-transformer-tap-changer-support
Jerry-Jinfeng-Guo Nov 11, 2024
0afeedc
Merge branch 'feature/step-up-transformer-tap-changer-support' into f…
Jerry-Jinfeng-Guo Nov 11, 2024
a68f7de
add case to validator
nitbharambe Nov 11, 2024
a1d8a37
spell error
nitbharambe Nov 11, 2024
402253e
Merge branch 'feature/step-up-transformer-tap-changer-support' into f…
nitbharambe Nov 13, 2024
d7f9b46
Merge pull request #829 from PowerGridModel/feature/regulator-unique-…
TonyXiang8787 Nov 13, 2024
66d6683
address comments
Jerry-Jinfeng-Guo Nov 14, 2024
4d390d5
minor
Jerry-Jinfeng-Guo Nov 14, 2024
40748ce
Merge branch 'feature/step-up-transformer-tap-changer-support' into f…
Jerry-Jinfeng-Guo Nov 14, 2024
6b98146
f formatter
Jerry-Jinfeng-Guo Nov 14, 2024
0f668d1
Merge pull request #698 from PowerGridModel/feature/updated-transform…
mgovers Nov 14, 2024
8dd99ca
Merge branch 'main' into feature/step-up-transformer-tap-changer-support
Jerry-Jinfeng-Guo Nov 14, 2024
1f2e30f
init branch
Jerry-Jinfeng-Guo Nov 14, 2024
0105588
Merge branch 'main' into feature/step-up-transformer-tap-changer-support
Jerry-Jinfeng-Guo Nov 15, 2024
6aeb317
Merge branch 'feature/step-up-transformer-tap-changer-support' into f…
Jerry-Jinfeng-Guo Nov 15, 2024
fa55bb4
made control side information available to trafo graph
Jerry-Jinfeng-Guo Nov 15, 2024
fc2efee
logic edges in place
Jerry-Jinfeng-Guo Nov 16, 2024
3a910f5
minor re-organize
Jerry-Jinfeng-Guo Nov 17, 2024
04b173c
tap side and control side made available to ranking
Jerry-Jinfeng-Guo Nov 17, 2024
f83b690
fixed a bug on the three winding transformer edge
Jerry-Jinfeng-Guo Nov 18, 2024
5ae08c5
full logic in place -> validation fail
Jerry-Jinfeng-Guo Nov 18, 2024
6ae1f7c
Merge branch 'main' into feature/step-up-transformer-tap-changer-support
Jerry-Jinfeng-Guo Nov 18, 2024
bd59bf5
Merge branch 'feature/step-up-transformer-tap-changer-support' into f…
Jerry-Jinfeng-Guo Nov 18, 2024
b642c97
updated validations test; marked invalid grid; added error interfacing
Jerry-Jinfeng-Guo Nov 19, 2024
da85da9
address comments
Jerry-Jinfeng-Guo Nov 21, 2024
8ceffe1
code coverage
Jerry-Jinfeng-Guo Nov 22, 2024
a5318ae
sonar and test grid asci art in test
Jerry-Jinfeng-Guo Nov 22, 2024
113f138
sonar
Jerry-Jinfeng-Guo Nov 22, 2024
ebefba6
comments addressed
Jerry-Jinfeng-Guo Nov 22, 2024
83c45f6
Merge pull request #832 from PowerGridModel/feature/step-up-trafo-tap…
Jerry-Jinfeng-Guo Nov 22, 2024
e3cfae2
Merge branch 'main' into feature/step-up-transformer-tap-changer-support
Jerry-Jinfeng-Guo Nov 22, 2024
8465085
Merge branch 'main' into feature/step-up-transformer-tap-changer-support
Jerry-Jinfeng-Guo Nov 25, 2024
a40ba45
Merge branch 'main' into feature/step-up-transformer-tap-changer-support
Jerry-Jinfeng-Guo Nov 27, 2024
abad3c8
Merge branch 'main' into feature/step-up-transformer-tap-changer-support
Jerry-Jinfeng-Guo Dec 3, 2024
fc2f20f
Merge branch 'main' into feature/step-up-transformer-tap-changer-support
Jerry-Jinfeng-Guo Dec 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/user_manual/calculations.md
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,7 @@ Power flow calculations that take the behavior of these regulators into account

The following control logic is used:

- Regulated transformers are ranked according to how close they are to {hoverxreftooltip}`sources <user_manual/components:source>` in terms of the amount of transformers inbetween.
- 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.
- Transformers are regulated in order according to their ranks.
- Initialize all transformers to their starting tap position (see {hoverxreftooltip}`user_manual/calculations:Initialization and exploitation of regulated transformers`)
- Find the optimal state using the following procedure
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ enum class BranchSide : IntS { from = 0, to = 1 };

enum class Branch3Side : IntS { side_1 = 0, side_2 = 1, side_3 = 2 };

enum class TapSide : IntS { from = 0, to = 1, side_1 = 0, side_2 = 1, side_3 = 2 };

enum class ControlSide : IntS { from = 0, to = 1, side_1 = 0, side_2 = 1, side_3 = 2 };

enum class CalculationType : IntS { power_flow = 0, state_estimation = 1, short_circuit = 2 };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,13 @@ class AutomaticTapCalculationError : public PowerGridError {
}
};

class AutomaticTapInputError : public PowerGridError {
public:
AutomaticTapInputError(std::string const& msg) {
append_msg("Automatic tap changer has invalid configuration. " + msg); // NOSONAR
}
};

class IDWrongType : public PowerGridError {
public:
explicit IDWrongType(ID id) { append_msg("Wrong type for object with id " + detail::to_string(id) + '\n'); }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,28 +137,8 @@ inline void add_component(MainModelState<ComponentContainer>& state, ForwardIter
}
}();

if (regulated_object_idx.group == get_component_type_index<Transformer>(state)) {
auto const& regulated_object = get_component<Transformer>(state, regulated_object_idx);

auto const non_tap_side =
regulated_object.tap_side() == BranchSide::from ? BranchSide::to : BranchSide::from;
if (get_component<Node>(state, regulated_object.node(regulated_object.tap_side())).u_rated() <
get_component<Node>(state, regulated_object.node(non_tap_side)).u_rated()) {
throw AutomaticTapCalculationError(id);
}
} else if (regulated_object_idx.group == get_component_type_index<ThreeWindingTransformer>(state)) {
auto const& regulated_object = get_component<ThreeWindingTransformer>(state, regulated_object_idx);
auto const tap_side_u_rated =
get_component<Node>(state, regulated_object.node(regulated_object.tap_side())).u_rated();
for (auto const side : branch3_sides) {
if (side == regulated_object.tap_side()) {
continue;
}
if (tap_side_u_rated < get_component<Node>(state, regulated_object.node(side)).u_rated()) {
throw AutomaticTapCalculationError(id);
}
}
} else {
if (regulated_object_idx.group != get_component_type_index<Transformer>(state) &&
regulated_object_idx.group != get_component_type_index<ThreeWindingTransformer>(state)) {
throw InvalidRegulatedObject(input.regulated_object, Component::name);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ using RankedTransformerGroups = std::vector<std::vector<Idx2D>>;

constexpr auto infty = std::numeric_limits<Idx>::max();
constexpr Idx2D unregulated_idx = {-1, -1};

struct TrafoGraphVertex {
bool is_source{};
};
Expand All @@ -66,12 +65,36 @@ struct TrafoGraphEdge {
}
};

constexpr TrafoGraphEdge unregulated_edge_prop = {unregulated_idx, 0};
using TrafoGraphEdges = std::vector<std::pair<TrafoGraphIdx, TrafoGraphIdx>>;
using TrafoGraphEdgeProperties = std::vector<TrafoGraphEdge>;

struct RegulatedTrafoProperties {
Idx id{};
ControlSide control_side{};

auto operator<=>(RegulatedTrafoProperties const& other) const = default; // NOLINT(modernize-use-nullptr)
};

using RegulatedTrafos = std::set<RegulatedTrafoProperties>;

inline std::pair<bool, ControlSide> regulated_trafos_contain(RegulatedTrafos const& trafos_set, Idx const& id) {
if (auto it =
std::ranges::find_if(trafos_set, [&](RegulatedTrafoProperties const& trafo) { return trafo.id == id; });
it != trafos_set.end()) {
return {true, it->control_side};
}
return {false, ControlSide{}}; // no default invalid control side, won't be used by logic
}

struct RegulatedObjects {
std::set<Idx> transformers{};
std::set<Idx> transformers3w{};
RegulatedTrafos trafos{};
RegulatedTrafos trafos3w{};

std::pair<bool, ControlSide> contains_trafo(Idx const& id) const { return regulated_trafos_contain(trafos, id); }
std::pair<bool, ControlSide> contains_trafo3w(Idx const& id) const {
return regulated_trafos_contain(trafos3w, id);
}
};

using TransformerGraph = boost::compressed_sparse_row_graph<boost::directedS, TrafoGraphVertex, TrafoGraphEdge,
Expand All @@ -90,12 +113,12 @@ inline void add_to_edge(main_core::MainModelState<ComponentContainer> const& sta

inline void process_trafo3w_edge(main_core::main_model_state_c auto const& state,
ThreeWindingTransformer const& transformer3w, bool const& trafo3w_is_regulated,
Idx2D const& trafo3w_idx, TrafoGraphEdges& edges,
ControlSide const& control_side, Idx2D const& trafo3w_idx, TrafoGraphEdges& edges,
TrafoGraphEdgeProperties& edge_props) {
using enum Branch3Side;

constexpr std::array<std::tuple<Branch3Side, Branch3Side>, 3> const branch3_combinations{
{{side_1, side_2}, {side_2, side_3}, {side_3, side_1}}};
{{side_1, side_2}, {side_1, side_3}, {side_2, side_3}}};

for (auto const& [first_side, second_side] : branch3_combinations) {
if (!transformer3w.status(first_side) || !transformer3w.status(second_side)) {
Expand All @@ -105,19 +128,24 @@ inline void process_trafo3w_edge(main_core::main_model_state_c auto const& state
auto const& to_node = transformer3w.node(second_side);

auto const tap_at_first_side = transformer3w.tap_side() == first_side;
auto const single_direction_condition =
trafo3w_is_regulated && (tap_at_first_side || transformer3w.tap_side() == second_side);
// ranking
if (single_direction_condition) {
auto const connected_to_primary_side_regulated =
trafo3w_is_regulated && (tap_at_first_side || (transformer3w.tap_side() == second_side));

auto const tap_at_control = static_cast<IntS>(control_side) == static_cast<IntS>(transformer3w.tap_side());

// only add weighted edge if the trafo3w meets the condition
if (connected_to_primary_side_regulated) {
auto const& tap_side_node = tap_at_first_side ? from_node : to_node;
auto const& non_tap_side_node = tap_at_first_side ? to_node : from_node;
auto const edge_from_node = tap_at_control ? non_tap_side_node : tap_side_node;
auto const edge_to_node = tap_at_control ? tap_side_node : non_tap_side_node;
// add regulated idx only when the first side node is tap side node.
// This is done to add only one directional edge with regulated idx.
Idx2D const regulated_idx = from_node == tap_side_node ? unregulated_idx : trafo3w_idx;
add_to_edge(state, edges, edge_props, tap_side_node, non_tap_side_node, {regulated_idx, 1});
auto const edge_value = TrafoGraphEdge{trafo3w_idx, 1};
add_to_edge(state, edges, edge_props, edge_from_node, edge_to_node, edge_value);
} else {
add_to_edge(state, edges, edge_props, from_node, to_node, {unregulated_idx, 1});
add_to_edge(state, edges, edge_props, to_node, from_node, {unregulated_idx, 1});
add_to_edge(state, edges, edge_props, from_node, to_node, unregulated_edge_prop);
add_to_edge(state, edges, edge_props, to_node, from_node, unregulated_edge_prop);
}
}
}
Expand All @@ -129,9 +157,10 @@ constexpr void add_edge(main_core::MainModelState<ComponentContainer> const& sta
TrafoGraphEdgeProperties& edge_props) {

for (auto const& transformer3w : state.components.template citer<ThreeWindingTransformer>()) {
bool const trafo3w_is_regulated = regulated_objects.transformers3w.contains(transformer3w.id());
auto const trafo3w_is_regulated = regulated_objects.contains_trafo3w(transformer3w.id());
Idx2D const trafo3w_idx = main_core::get_component_idx_by_id(state, transformer3w.id());
process_trafo3w_edge(state, transformer3w, trafo3w_is_regulated, trafo3w_idx, edges, edge_props);
process_trafo3w_edge(state, transformer3w, trafo3w_is_regulated.first, trafo3w_is_regulated.second, trafo3w_idx,
edges, edge_props);
}
}

Expand All @@ -146,15 +175,17 @@ constexpr void add_edge(main_core::MainModelState<ComponentContainer> const& sta
}
auto const& from_node = transformer.from_node();
auto const& to_node = transformer.to_node();
if (regulated_objects.transformers.contains(transformer.id())) {
auto const tap_at_from_side = transformer.tap_side() == BranchSide::from;
auto const& tap_side_node = tap_at_from_side ? from_node : to_node;
auto const& non_tap_side_node = tap_at_from_side ? to_node : from_node;
add_to_edge(state, edges, edge_props, tap_side_node, non_tap_side_node,
{main_core::get_component_idx_by_id(state, transformer.id()), 1});
auto const trafo_regulated = regulated_objects.contains_trafo(transformer.id());
if (trafo_regulated.first) {
auto const control_side = trafo_regulated.second;
auto const control_side_node = control_side == ControlSide::from ? from_node : to_node;
auto const non_control_side_node = control_side == ControlSide::from ? to_node : from_node;
auto const trafo_idx = main_core::get_component_idx_by_id(state, transformer.id());

add_to_edge(state, edges, edge_props, non_control_side_node, control_side_node, {trafo_idx, 1});
} else {
add_to_edge(state, edges, edge_props, from_node, to_node, {unregulated_idx, 1});
add_to_edge(state, edges, edge_props, to_node, from_node, {unregulated_idx, 1});
add_to_edge(state, edges, edge_props, from_node, to_node, unregulated_edge_prop);
add_to_edge(state, edges, edge_props, to_node, from_node, unregulated_edge_prop);
}
}
}
Expand All @@ -172,8 +203,8 @@ constexpr void add_edge(main_core::MainModelState<ComponentContainer> const& sta
if (!branch.from_status() || !branch.to_status()) {
continue;
}
add_to_edge(state, edges, edge_props, branch.from_node(), branch.to_node(), {unregulated_idx, 0});
add_to_edge(state, edges, edge_props, branch.to_node(), branch.from_node(), {unregulated_idx, 0});
add_to_edge(state, edges, edge_props, branch.from_node(), branch.to_node(), unregulated_edge_prop);
add_to_edge(state, edges, edge_props, branch.to_node(), branch.from_node(), unregulated_edge_prop);
}
}

Expand All @@ -190,10 +221,11 @@ inline auto retrieve_regulator_info(State const& state) -> RegulatedObjects {
if (!regulator.status()) {
continue;
}
auto const control_side = regulator.control_side();
if (regulator.regulated_object_type() == ComponentType::branch) {
regulated_objects.transformers.emplace(regulator.regulated_object());
regulated_objects.trafos.emplace(RegulatedTrafoProperties{regulator.regulated_object(), control_side});
} else {
regulated_objects.transformers3w.emplace(regulator.regulated_object());
regulated_objects.trafos3w.emplace(RegulatedTrafoProperties{regulator.regulated_object(), control_side});
}
}
return regulated_objects;
Expand Down Expand Up @@ -243,7 +275,8 @@ inline void process_edges_dijkstra(Idx v, std::vector<EdgeWeight>& vertex_distan
auto t = boost::target(e, graph);
const EdgeWeight weight = graph[e].weight;

// We can not use BGL_FORALL_OUTEDGES here because our grid is undirected
// We can not use BGL_FORALL_OUTEDGES here because we need information
// regardless of edge direction
if (u == s && vertex_distances[s] + weight < vertex_distances[t]) {
vertex_distances[t] = vertex_distances[s] + weight;
pq.push({vertex_distances[t], t});
Expand All @@ -270,9 +303,33 @@ inline auto get_edge_weights(TransformerGraph const& graph) -> TrafoGraphEdgePro
if (graph[e].regulated_idx == unregulated_idx) {
continue;
}
auto edge_res = std::min(vertex_distances[boost::source(e, graph)], vertex_distances[boost::target(e, graph)]);
// control_side, two winding or three winding, is always parallel to the
// source side of the transformer edge
auto const edge_src_rank = vertex_distances[boost::source(e, graph)];
auto const edge_tgt_rank = vertex_distances[boost::target(e, graph)];
auto const edge_res = std::min(edge_src_rank, edge_tgt_rank);

// New edge logic for ranking
// | Tap | Control | All edges |
// ---------------------------------------------
// | A | A | [B->A], [C->A], [B<->C] |
// | A | B | [A->B], [A->C], [B<->C] |
// | A | C | [A->B], [A->C], [B<->C] |
// | B | A | [B->A], [C<->A], [B->C] |
// | B | B | [A->B], [C<->A], [C->B] |
// | B | C | [B->A], [C<->A], [B->C] |
// | C | A | [A<->B], [C->A], [C->B] |
// | C | B | [A<->B], [C->A], [C->B] |
// | C | C | [A<->B], [A->C], [A->B] |
// In two winding trafo, the edge is always pointing to the control side; in three winding trafo edges, the
// edges are always pointing parallel to the control side: meaning that for delta configuration ABC, the above
// situations can happen.
if (edge_src_rank != edge_tgt_rank - 1) {
throw AutomaticTapInputError(
"Control side of a transformer should be the relatively closer side to a source.\n");
}
if (!is_unreachable(edge_res)) {
result.push_back({graph[e].regulated_idx, edge_res});
result.emplace_back(TrafoGraphEdge{graph[e].regulated_idx, edge_tgt_rank});
}
}

Expand All @@ -292,7 +349,10 @@ inline auto rank_transformers(TrafoGraphEdgeProperties const& w_trafo_list) -> R
groups.emplace_back();
previous_weight = trafo.weight;
}
groups.back().push_back(trafo.regulated_idx);
auto& current_group = groups.back(); // avoid duplicates
if (std::ranges::find(current_group, trafo.regulated_idx) == current_group.end()) {
current_group.emplace_back(trafo.regulated_idx);
}
}
return groups;
}
Expand Down
4 changes: 4 additions & 0 deletions src/power_grid_model/_core/error_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from power_grid_model._core.power_grid_core import power_grid_core as pgc
from power_grid_model.errors import (
AutomaticTapCalculationError,
AutomaticTapInputError,
ConflictID,
ConflictVoltage,
IDNotFound,
Expand Down Expand Up @@ -71,6 +72,8 @@
_AUTOMATIC_TAP_CALCULATION_ERROR_RE = re.compile(
r"Automatic tap changing regulator with tap_side at LV side is not supported. Found at id (-?\d+)\n"
)
_AUTOMATIC_TAP_INPUT_ERROR_RE = re.compile(r"Automatic tap changer has invalid configuration\..*")

_ID_WRONG_TYPE_RE = re.compile(r"Wrong type for object with id (-?\d+)\n")
_INVALID_CALCULATION_METHOD_RE = re.compile(r"The calculation method is invalid for this calculation!")
_INVALID_SHORT_CIRCUIT_PHASE_OR_TYPE_RE = re.compile(r"short circuit type") # multiple different flavors
Expand All @@ -95,6 +98,7 @@
_INVALID_MEASURED_OBJECT_RE: InvalidMeasuredObject,
_INVALID_REGULATED_OBJECT_RE: InvalidRegulatedObject,
_AUTOMATIC_TAP_CALCULATION_ERROR_RE: AutomaticTapCalculationError,
_AUTOMATIC_TAP_INPUT_ERROR_RE: AutomaticTapInputError,
_ID_WRONG_TYPE_RE: IDWrongType,
_INVALID_CALCULATION_METHOD_RE: InvalidCalculationMethod,
_INVALID_SHORT_CIRCUIT_PHASE_OR_TYPE_RE: InvalidShortCircuitPhaseOrType,
Expand Down
4 changes: 4 additions & 0 deletions src/power_grid_model/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ class AutomaticTapCalculationError(PowerGridError):
"""Automatic tap changer with tap at LV side is unsupported for automatic tap changing calculation."""


class AutomaticTapInputError(PowerGridError):
"""Automatic tap changer has invalid configuration."""


class InvalidShortCircuitPhaseOrType(PowerGridError):
"""Invalid (combination of) short circuit types and phase(s) provided."""

Expand Down
1 change: 1 addition & 0 deletions src/power_grid_model/validation/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -978,6 +978,7 @@ def validate_regulator(data: SingleDataset, component: ComponentType) -> list[Va
def validate_transformer_tap_regulator(data: SingleDataset) -> list[ValidationError]:
errors = validate_regulator(data, ComponentType.transformer_tap_regulator)
errors += _all_boolean(data, ComponentType.transformer_tap_regulator, "status")
errors += _all_unique(data, ComponentType.transformer_tap_regulator, "regulated_object")
errors += _all_valid_enum_values(
data, ComponentType.transformer_tap_regulator, "control_side", [BranchSide, Branch3Side]
)
Expand Down
Loading
Loading