Skip to content

Commit 80a9c4d

Browse files
Merge pull request #1011 from PowerGridModel/feature/accept-periodic-clock-number-in-transformer-input
Transformer: support periodic clock input
2 parents d1b975a + 57a9e24 commit 80a9c4d

File tree

11 files changed

+83
-22
lines changed

11 files changed

+83
-22
lines changed

docs/examples/Transformer Examples.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@
386386
"three_winding_transformer[\"winding_2\"] = [1]\n",
387387
"three_winding_transformer[\"winding_3\"] = [1]\n",
388388
"three_winding_transformer[\"clock_12\"] = [5]\n",
389-
"three_winding_transformer[\"clock_13\"] = [5]\n",
389+
"three_winding_transformer[\"clock_13\"] = [-7] # supports periodic input\n",
390390
"three_winding_transformer[\"tap_side\"] = [0]\n",
391391
"three_winding_transformer[\"tap_pos\"] = [0]\n",
392392
"three_winding_transformer[\"tap_min\"] = [-10]\n",

docs/user_manual/components.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ An example of usage of transformer is given in [Transformer Examples](../example
196196
| `p0` | `double` | watt (W) | no-load (iron) loss | ✔ | ❌ | `>= 0` |
197197
| `winding_from` | {py:class}`WindingType <power_grid_model.enum.WindingType>` | - | from-side winding type | &#10004; | &#10060; | |
198198
| `winding_to` | {py:class}`WindingType <power_grid_model.enum.WindingType>` | - | to-side winding type | &#10004; | &#10060; | |
199-
| `clock` | `int8_t` | - | clock number of phase shift. Even number is not possible if one side is Y(N) winding and the other side is not Y(N) winding. Odd number is not possible, if both sides are Y(N) winding or both sides are not Y(N) winding. | &#10004; | &#10060; | `>= 0` and `<= 12` |
199+
| `clock` | `int8_t` | - | clock number of phase shift. Even number is not possible if one side is Y(N) winding and the other side is not Y(N) winding. Odd number is not possible, if both sides are Y(N) winding or both sides are not Y(N) winding. | &#10004; | &#10060; | `>= -12` and `<= 12` |
200200
| `tap_side` | {py:class}`BranchSide <power_grid_model.enum.BranchSide>` | - | side of tap changer | &#10004; | &#10060; | |
201201
| `tap_pos` | `int8_t` | - | current position of tap changer | &#10060; default `tap_nom`, if no `tap_nom` default `0` | &#10004; | `(tap_min <= tap_pos <= tap_max)` or `(tap_min >= tap_pos >= tap_max)` |
202202
| `tap_min` | `int8_t` | - | position of tap changer at minimum voltage | &#10004; | &#10060; | |
@@ -514,8 +514,8 @@ An example of usage of three-winding transformer is given in
514514
| `winding_1` | {py:class}`WindingType <power_grid_model.enum.WindingType>` | - | side 1 winding type | &#10004; | &#10060; | |
515515
| `winding_2` | {py:class}`WindingType <power_grid_model.enum.WindingType>` | - | side 2 winding type | &#10004; | &#10060; | |
516516
| `winding_3` | {py:class}`WindingType <power_grid_model.enum.WindingType>` | - | side 3 winding type | &#10004; | &#10060; | |
517-
| `clock_12` | `int8_t` | - | clock number of phase shift across side 1-2, odd number is only allowed for Dy(n) or Y(N)d configuration. | &#10004; | &#10060; | `>= 0` and `<= 12` |
518-
| `clock_13` | `int8_t` | - | clock number of phase shift across side 1-3, odd number is only allowed for Dy(n) or Y(N)d configuration. | &#10004; | &#10060; | `>= 0` and `<= 12` |
517+
| `clock_12` | `int8_t` | - | clock number of phase shift across side 1-2, odd number is only allowed for Dy(n) or Y(N)d configuration. | &#10004; | &#10060; | `>= -12` and `<= 12` |
518+
| `clock_13` | `int8_t` | - | clock number of phase shift across side 1-3, odd number is only allowed for Dy(n) or Y(N)d configuration. | &#10004; | &#10060; | `>= -12` and `<= 12` |
519519
| `tap_side` | {py:class}`Branch3Side <power_grid_model.enum.Branch3Side>` | - | side of tap changer | &#10004; | &#10060; | `side_1` or `side_2` or `side_3` |
520520
| `tap_pos` | `int8_t` | - | current position of tap changer | &#10060; default `tap_nom`, if no `tap_nom` default `0` | &#10004; | `(tap_min <= tap_pos <= tap_max)` or `(tap_min >= tap_pos >= tap_max)` |
521521
| `tap_min` | `int8_t` | - | position of tap changer at minimum voltage | &#10004; | &#10060; | |

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#pragma once
66

7+
#include <cmath>
78
#include <complex>
89
#include <cstddef>
910
#include <cstdint>
@@ -108,4 +109,21 @@ struct IncludeAll {
108109
};
109110
constexpr IncludeAll include_all{};
110111

112+
// function to handle periodic mapping
113+
template <typename T> constexpr T map_to_cyclic_range(T value, T period) {
114+
static_assert(std::is_arithmetic_v<T>, "T must be an arithmetic type (integral or floating-point)");
115+
if constexpr (std::is_integral_v<T>) {
116+
return static_cast<T>((value % period + period) % period);
117+
} else {
118+
if (std::is_constant_evaluated()) {
119+
T quotient = value / period;
120+
Idx const floored_quotient =
121+
(quotient >= T{0}) ? static_cast<Idx>(quotient) : static_cast<Idx>(quotient) - 1;
122+
T result = value - static_cast<T>(floored_quotient) * period;
123+
return result;
124+
}
125+
return std::fmod(std::fmod(value, period) + period, period);
126+
}
127+
}
128+
111129
} // namespace power_grid_model

power_grid_model_c/power_grid_model/include/power_grid_model/component/three_winding_transformer.hpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,9 @@ class ThreeWindingTransformer : public Branch3 {
9393
throw InvalidTransformerClock{id(), clock_13_};
9494
}
9595

96-
// set clock to zero if it is 12
97-
clock_12_ = static_cast<IntS>(clock_12_ % 12);
98-
clock_13_ = static_cast<IntS>(clock_13_ % 12);
96+
// handle periodic clock input -> in range [0, 11]
97+
clock_12_ = map_to_cyclic_range(clock_12_, IntS{12});
98+
clock_13_ = map_to_cyclic_range(clock_13_, IntS{12});
9999
// check tap bounds
100100
tap_pos_ = tap_limit(tap_pos_);
101101
}
@@ -119,6 +119,8 @@ class ThreeWindingTransformer : public Branch3 {
119119
constexpr IntS tap_min() const { return tap_min_; }
120120
constexpr IntS tap_max() const { return tap_max_; }
121121
constexpr IntS tap_nom() const { return tap_nom_; }
122+
constexpr IntS clock_12() const { return clock_12_; }
123+
constexpr IntS clock_13() const { return clock_13_; }
122124

123125
// setter
124126
constexpr bool set_tap(IntS new_tap) {

power_grid_model_c/power_grid_model/include/power_grid_model/component/transformer.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ class Transformer : public Branch {
6363
throw InvalidTransformerClock{id(), clock_};
6464
}
6565

66-
// set clock to zero if it is 12
67-
clock_ = static_cast<IntS>(clock_ % 12);
66+
// handle periodic clock input -> in range [0, 11]
67+
clock_ = map_to_cyclic_range(clock_, IntS{12});
6868
// check tap bounds
6969
tap_pos_ = tap_limit(tap_pos_);
7070
}
@@ -82,6 +82,7 @@ class Transformer : public Branch {
8282
constexpr IntS tap_min() const { return tap_min_; }
8383
constexpr IntS tap_max() const { return tap_max_; }
8484
constexpr IntS tap_nom() const { return tap_nom_; }
85+
constexpr IntS clock() const { return clock_; }
8586

8687
// setter
8788
constexpr bool set_tap(IntS new_tap) {

power_grid_model_c/power_grid_model/include/power_grid_model/component/transformer_utils.hpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,14 @@ constexpr double tap_adjust_impedance(double tap_pos, double tap_min, double tap
4545
constexpr bool is_valid_clock(IntS clock, WindingType winding_from, WindingType winding_to) {
4646
using enum WindingType;
4747

48-
bool const clock_in_range = 0 <= clock && clock <= 12;
4948
bool const clock_is_even = (clock % 2) == 0;
5049

5150
bool const is_from_wye = winding_from == wye || winding_from == wye_n;
5251
bool const is_to_wye = winding_to == wye || winding_to == wye_n;
5352

5453
// even clock number is only possible when both sides are wye winding or both sides aren't
5554
// and conversely for odd clock number
56-
bool const correct_clock_winding = (clock_is_even == (is_from_wye == is_to_wye));
57-
58-
return clock_in_range && correct_clock_winding;
55+
return (clock_is_even == (is_from_wye == is_to_wye));
5956
}
6057

6158
// add tap

src/power_grid_model/validation/_validation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,7 @@ def validate_transformer(data: SingleDataset) -> list[ValidationError]:
607607
errors += _all_greater_than_or_equal_to_zero(data, ComponentType.transformer, "p0")
608608
errors += _all_valid_enum_values(data, ComponentType.transformer, "winding_from", WindingType)
609609
errors += _all_valid_enum_values(data, ComponentType.transformer, "winding_to", WindingType)
610-
errors += _all_between_or_at(data, ComponentType.transformer, "clock", 0, 12)
610+
errors += _all_between_or_at(data, ComponentType.transformer, "clock", -12, 12)
611611
errors += _all_valid_clocks(data, ComponentType.transformer, "clock", "winding_from", "winding_to")
612612
errors += _all_valid_enum_values(data, ComponentType.transformer, "tap_side", BranchSide)
613613
errors += _all_between_or_at(

tests/cpp_unit_tests/test_common.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,15 @@ static_assert(include_all(1));
2121

2222
// NOLINTNEXTLINE(performance-move-const-arg,hicpp-move-const-arg) // to test that rvalues work
2323
static_assert(include_all(Idx{2}, std::move(Idx{3}))); // NOSONAR // to test that rvalues work
24+
25+
// periodic mapping
26+
static_assert(map_to_cyclic_range(5, 3) == 2);
27+
static_assert(map_to_cyclic_range(5.0, 3.0) == 2.0);
28+
static_assert(map_to_cyclic_range(-1, 3) == 2);
29+
static_assert(map_to_cyclic_range(-1.0, 3.0) == 2.0);
30+
static_assert(map_to_cyclic_range(12, 12) == 0);
31+
static_assert(map_to_cyclic_range(13, 12) == 1);
32+
static_assert(map_to_cyclic_range(11, 12) == 11);
33+
static_assert(map_to_cyclic_range(-1, -3) == -1);
2434
} // namespace
2535
} // namespace power_grid_model

tests/cpp_unit_tests/test_three_winding_transformer.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,28 @@ TEST_CASE("Test three winding transformer") {
484484
input.node_2 = 3;
485485
}
486486

487+
SUBCASE("Periodic clock input") {
488+
input.clock_12 = 24;
489+
input.clock_13 = 37;
490+
ThreeWindingTransformer const trafo_24_36(input, 138e3, 69e3, 13.8e3);
491+
CHECK(trafo_24_36.clock_12() == 0);
492+
CHECK(trafo_24_36.clock_13() == 1);
493+
494+
input.clock_12 = -2;
495+
input.clock_13 = -13;
496+
ThreeWindingTransformer const trafo_m2_m13(input, 138e3, 69e3, 13.8e3);
497+
CHECK(trafo_m2_m13.clock_12() == 10);
498+
CHECK(trafo_m2_m13.clock_13() == 11);
499+
500+
input.winding_2 = WindingType::delta;
501+
input.winding_3 = WindingType::delta;
502+
input.clock_12 = 25;
503+
input.clock_13 = 13;
504+
ThreeWindingTransformer const trafo_25_13(input, 138e3, 69e3, 13.8e3);
505+
CHECK(trafo_25_13.clock_12() == 1);
506+
CHECK(trafo_25_13.clock_13() == 1);
507+
}
508+
487509
SUBCASE("Test i base") {
488510
CHECK(vec[0].base_i_1() == doctest::Approx(base_i_1));
489511
CHECK(vec[0].base_i_2() == doctest::Approx(base_i_2));

tests/cpp_unit_tests/test_transformer.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,23 @@ TEST_CASE("Test transformer") {
161161
CHECK(vec[0].tap_pos() == 9);
162162
}
163163

164+
SUBCASE("periodic clock input") {
165+
input.clock = 24;
166+
Transformer const trafo_24(input, 150.0e3, 10.0e3);
167+
input.clock = 36;
168+
Transformer const trafo_36(input, 150.0e3, 10.0e3);
169+
input.clock = -2;
170+
Transformer const trafo_m2(input, 150.0e3, 10.0e3);
171+
CHECK(trafo_24.clock() == 0);
172+
CHECK(trafo_36.clock() == 0);
173+
CHECK(trafo_m2.clock() == 10);
174+
175+
input.winding_to = WindingType::delta;
176+
input.clock = 25;
177+
Transformer const trafo_25(input, 150.0e3, 10.0e3);
178+
CHECK(trafo_25.clock() == 1);
179+
}
180+
164181
SUBCASE("symmetric parameters") {
165182
for (size_t i = 0; i < 5; i++) {
166183
auto changed =

0 commit comments

Comments
 (0)