diff --git a/docs/graph-data-model.md b/docs/graph-data-model.md index d99197565f..2e242aacc0 100644 --- a/docs/graph-data-model.md +++ b/docs/graph-data-model.md @@ -6,35 +6,31 @@ SPDX-License-Identifier: MPL-2.0 # Graph Data Model -To represent the physical grid components, and the calculation results, -this library uses a graph data model. -In this document, the graph data model is presented with the list of all components types, -and their relevant input/output attributes. +To represent the physical grid components, and the calculation results, this library uses a graph data model. In this +document, the graph data model is presented with the list of all components types, and their relevant input/output +attributes. # Enumerations -Some attributes of components are enumerations. -The enumerations are implemented using 8-bit signed integer, as explained in +Some attributes of components are enumerations. The enumerations are implemented using 8-bit signed integer, as +explained in [Native Data Interface](native-data-interface.md). -The table below for a list of enumerations. -They are all defined in the module `power_grid_model.enum`. -The underlying type of enumeration is `int8_t`. - -| enum type name in Python | possible values | usage | -| --- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| --- | -| `LoadGenType` | `const_power = 0`
`const_impedance = 1`
`const_current = 2` | load/generation types | -| `WindingType` | `wye = 0`
`wye_n = 1`
`delta = 2`
`zigzag = 3`
`zigzag_n = 4` | transformer winding type | -| `BranchSide` | `from_side = 0`
`to_side = 1` | the side of a branch | -| `MeasuredTerminalType` | `branch_from = 0`, measuring the from-terminal between a branch and a node
`branch_to = 1`, measuring the to-terminal between a branch and a node
`source = 2`, measuring the terminal between a source and a node
`shunt = 3`, measuring the terminal between a shunt and a node
`load = 4`, measuring the terminal between a load and a node
`generator = 5`, measuring the terminal between a generator and a node | type of flow (e.g. power) measurement | -| `CalculationMethod` | `linear = 0`
`newton_raphson = 1`
`iterative_linear = 2`
`iterative_current = 3`
`linear_current = 4` | method of calculation | +The table below for a list of enumerations. They are all defined in the module `power_grid_model.enum`. The underlying +type of enumeration is `int8_t`. +| enum type name in Python | possible values | usage | +| --- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| --- | +| `LoadGenType` | `const_power = 0`
`const_impedance = 1`
`const_current = 2` | load/generation types | +| `WindingType` | `wye = 0`
`wye_n = 1`
`delta = 2`
`zigzag = 3`
`zigzag_n = 4` | transformer winding type | +| `BranchSide` | `from_side = 0`
`to_side = 1`
`side_1 = 2`
`side_2 = 3`
`side_3 = 4` | the side of a branch | +| `MeasuredTerminalType` | `branch_from = 0`, measuring the from-terminal between a branch and a node
`branch_to = 1`, measuring the to-terminal between a branch and a node
`source = 2`, measuring the terminal between a source and a node
`shunt = 3`, measuring the terminal between a shunt and a node
`load = 4`, measuring the terminal between a load and a node
`generator = 5`, measuring the terminal between a generator and a node
`branch3_1 = 6`, measuring the terminal-1 between a branch3 and a node
`branch3_2 = 7`, measuring the terminal-2 between a branch3 and a node
`branch3_3 = 8`, measuring the terminal-3 between a branch3 and a node | type of flow (e.g. power) measurement | +| `CalculationMethod` | `linear = 0`
`newton_raphson = 1`
`iterative_linear = 2`
`iterative_current = 3`
`linear_current = 4` | method of calculation | # Component Type Hierarchy and Graph Data Model -The components types are organized in an inheritance-like hierarchy. -A sub-type has all the attributes from its parent type. -The hierarchy of the component types is shown below. +The components types are organized in an inheritance-like hierarchy. A sub-type has all the attributes from its parent +type. The hierarchy of the component types is shown below. ``` base ──┬─────────────────────────────────────────────── node @@ -42,6 +38,8 @@ base ──┬────────────────────── ├── branch ──────────────────────────────────┬── line │ ├── link │ └── transformer + | + |── branch3 ──────────────────────────────────── three_winding_transformer │ ├── appliance ──┬─────────────────────────────── source │ │ @@ -59,66 +57,67 @@ base ──┬────────────────────── └── asym_power_sensor ``` -**NOTE: the type names in the hierarchy are exactly the same as the component type names -in the `power_grid_model.power_grid_meta_data`, see [Native Data Interface](native-data-interface.md)** +**NOTE: the type names in the hierarchy are exactly the same as the component type names in +the `power_grid_model.power_grid_meta_data`, see [Native Data Interface](native-data-interface.md)** + +This library uses a graph data model with three generic component types: `node`, `branch`, `branch3` and `appliance`. A +node is similar to a vertex in the graph, a branch is similar to an edge in the graph and a branch3 connects threee +nodes together. An appliance is a component which is connected (coupled) to a node, it is seen as a user of this node. -This library uses a graph data model with three generic component types: `node`, `branch`, and `appliance`. -A node is similar to a vertex in the graph, a branch is similar to an edge in the graph. -An appliance is a component which is connected (coupled) to a node, it is seen as a user of this node. The figure below shows a simple example: ``` -node_1 ---line_3 (branch)--- node_2 - | | -source_5 (appliance) sym_load_4 (appliance) +node_1 ---line_3 (branch)--- node_2 --------------three_winding_transformer_8 (branch3)------ node_6 + | | | +source_5 (appliance) sym_load_4 (appliance) node_7 ``` -There are two nodes (points/vertices) in the graph of this simple grid. -The two nodes are connected by `line_3` which is a branch (edge). -Furthermore, there are two appliances in the grid. -The `source_5` is coupled to `node_1` and the `sym_load_4` is coupled to `node_2`. +* There are four nodes (points/vertices) in the graph of this simple grid. +* The `node_1` and `node_2` are connected by `line_3` which is a branch (edge). +* The `node_2`, `node_6`, and `node_7` are connected by `three_winding_transformer_8` which is a branch3. +* There are two appliances in the grid. The `source_5` is coupled to `node_1` and the `sym_load_4` is coupled + to `node_2`. # Symmetry of Components and Calculation -It should be emphasized that the symmetry of components and calculation are two independent concepts in the model. -For example, a power grid model can consist of both `sym_load` and `asym_load`. -They are symmetric or asymmetric load components. -On the other hand, the same model can execute symmetric or asymmetric calculations. -* In case of symmetric calculation, the `asym_load` will be treated as a symmetric load -by averaging the specified power through three phases. -* In case of asymmetric calculation, the `sym_load` will be treated as an asymmetric load -by dividing the total specified power equally into three phases. +It should be emphasized that the symmetry of components and calculation are two independent concepts in the model. For +example, a power grid model can consist of both `sym_load` and `asym_load`. They are symmetric or asymmetric load +components. On the other hand, the same model can execute symmetric or asymmetric calculations. + +* In case of symmetric calculation, the `asym_load` will be treated as a symmetric load by averaging the specified power + through three phases. +* In case of asymmetric calculation, the `sym_load` will be treated as an asymmetric load by dividing the total + specified power equally into three phases. # Attributes of Components -The attributes of components are listed in the tables in the sections below. -The column names of the tables are as follows: - -* name: name of the attribute. - It is exactly the same as the attribute name in `power_grid_model.power_grid_meta_data`. -* data type: data type of the attribute. - It is either a type from the table in [Native Data Interface](native-data-interface.md). - Or it can be an enumeration as above defined. There is two special data types `RealValueInput` and `RealValueOutput`. - * `RealValueInput` is used for some input attributes. - It is a `double` for a symmetric class (e.g. `sym_load`) and `double[3]` an asymmetric class (e.g. `asym_load`). - It is explained in detail in the corresponding types. - * `RealValueOutput` is used for many output attributes. - It is a `double` in symmetric calculation and `double[3]` for asymmetric calculation. +The attributes of components are listed in the tables in the sections below. The column names of the tables are as +follows: + +* name: name of the attribute. It is exactly the same as the attribute name in `power_grid_model.power_grid_meta_data`. +* data type: data type of the attribute. It is either a type from the table + in [Native Data Interface](native-data-interface.md). Or it can be an enumeration as above defined. There is two + special data types `RealValueInput` and `RealValueOutput`. + * `RealValueInput` is used for some input attributes. It is a `double` for a symmetric class (e.g. `sym_load`) + and `double[3]` an asymmetric class (e.g. `asym_load`). It is explained in detail in the corresponding types. + * `RealValueOutput` is used for many output attributes. It is a `double` in symmetric calculation and `double[3]` + for asymmetric calculation. * As noted above, these two special types are independent. -* unit: unit of the attribute, if it is applicable. As a general rule, only standard SI units without any prefix are used. +* unit: unit of the attribute, if it is applicable. As a general rule, only standard SI units without any prefix are + used. * description: description of the attribute. -* required: if the attribute is required. If not, then it is optional. - Note if you choose not to specify an optional attribute, - it should have the null value as defined in [Native Data Interface](native-data-interface.md). +* required: if the attribute is required. If not, then it is optional. Note if you choose not to specify an optional + attribute, it should have the null value as defined in [Native Data Interface](native-data-interface.md). * input: if the attribute is part of an input dataset. -* update: if the attribute can be mutated by the update call `PowerGridModel.update` on an existing instance, - only applicable when this attribute is part of an input dataset. +* update: if the attribute can be mutated by the update call `PowerGridModel.update` on an existing instance, only + applicable when this attribute is part of an input dataset. * output: if the attribute is part of an output dataset. * valid values: if applicable, an indication which values are valid for the input data # Validation -For performance reasons, the input/update data is not automatically validated. -There are validation functions available in the `power_grid_model.validation` module: + +For performance reasons, the input/update data is not automatically validated. There are validation functions available +in the `power_grid_model.validation` module: ```python # Manual validation @@ -130,16 +129,20 @@ validate_batch_data(input_data, update_data, calculation_type, symmetric) -> Dic # Assertions # assert_valid_input_data() and assert_valid_batch_data() raise a ValidationException, # containing the list/dict of errors, when the data is invalid. -assert_valid_input_data(input_data, calculation_type, symmetric) raises ValidationException -assert_valid_batch_data(input_data, calculation_type, update_data, symmetric) raises ValidationException +assert_valid_input_data(input_data, calculation_type, symmetric) +raises +ValidationException +assert_valid_batch_data(input_data, calculation_type, update_data, symmetric) +raises +ValidationException # Utilities # errors_to_string() converts a set of errors to a human readable (multi-line) string representation errors_to_string(errors, name, details) ``` -Have a look at the Jupyter Notebook "[Validation Examples](../examples/Validation%20Examples.ipynb)" for more information -on how to apply these functions. +Have a look at the Jupyter Notebook "[Validation Examples](../examples/Validation%20Examples.ipynb)" for more +information on how to apply these functions. # Component Types @@ -167,16 +170,13 @@ The base type for all power grid components. | `u_angle` | `RealValueOutput` | rad | voltage angle | | ❌ | ❌ | ✔ | | | `u` | `RealValueOutput` | volt (V) | voltage magnitude, line-line for symmetric calculation, line-neutral for asymmetric calculation | | ❌ | ❌ | ✔ | | - ## Branch * type name: `branch` -`branch` is the abstract base type for the component which connects two *different* nodes. -For each branch two switches are always defined at from- and to-side of the branch. -In reality such switches may not exist. -For example, a cable usually permanently connects two joints. -In this case, the attribute `from_status` and `to_status` is always 1. +`branch` is the abstract base type for the component which connects two *different* nodes. For each branch two switches +are always defined at from- and to-side of the branch. In reality such switches may not exist. For example, a cable +usually permanently connects two joints. In this case, the attribute `from_status` and `to_status` is always 1. | name | data type | unit | description | required | input | update | output | valid values | | --- | --- | --- | --- | :---: | :---: | :---: | :---: | :---: | @@ -194,13 +194,12 @@ In this case, the attribute `from_status` and `to_status` is always 1. | `s_to` | `RealValueOutput` | volt-ampere (VA) | apparent power flowing at to-side | | ❌ | ❌ | ✔ | | | `loading` | `double` | - | relative loading of the line, `1.0` meaning 100% loaded. | | ❌ | ❌ | ✔ | | - ### Line * type name: 'line' -`line` is a branch with specified serial impedance and shunt admittance. A cable is also modeled as `line`. -A `line` can only connect two nodes with the same rated voltage. +`line` is a branch with specified serial impedance and shunt admittance. A cable is also modeled as `line`. A `line` can +only connect two nodes with the same rated voltage. | name | data type | unit | description | required | input | update | output | valid values | | --- | --- | --- | --- | :---: | :---: | :---: | :---: | :---: | @@ -218,19 +217,16 @@ A `line` can only connect two nodes with the same rated voltage. * type name: `link` -`link` usually represents a short internal cable/connection between two busbars inside a substation. -It has a very high admittance (small impedance) which is set to a fixed per-unit value -(equivalent to 10e6 siemens for 10kV network). -There is no additional attribute for `link`. +`link` usually represents a short internal cable/connection between two busbars inside a substation. It has a very high +admittance (small impedance) which is set to a fixed per-unit value +(equivalent to 10e6 siemens for 10kV network). There is no additional attribute for `link`. ### Transformer `transformer` connects two nodes with possibly different voltage levels. -**Note: three-winding transformer is not supported yet.** - -**Note: it can happen that `tap_min > tap_max`. -In this case the winding voltage is decreased if the tap position is increased.** +**Note: it can happen that `tap_min > tap_max`. In this case the winding voltage is decreased if the tap position is +increased.** | name | data type | unit | description | required | input | update | output | valid values | | --- | --- | --- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| :---: | :---: | :---: | :---: | :---: | @@ -259,12 +255,94 @@ In this case the winding voltage is decreased if the tap position is increased.* | `r_grounding_to` | `double` | ohm (Ω) | grounding resistance at to-side, if relevant | ❌ default zero | ✔ | ❌ | ❌ | | | `x_grounding_to` | `double` | ohm (Ω) | grounding reactance at to-side, if relevant | ❌ default zero | ✔ | ❌ | ❌ | | +## Branch3 + +* type name: `branch3` + +`branch3` is the abstract base type for the component which connects three *different* nodes. For each branch3 three +switches are always defined at side 1, 2, or 3 of the branch. In reality such switches may not exist. + +| name | data type | unit | description | required | input | update | output | valid values | +| --- | --- | --- | --- | :---: | :---: | :---: | :---: | :---: | +| `node_1` | `int32_t` | - | ID of node at side 1 | ✔ | ✔ | ❌ | ❌ | a valid node id | +| `node_2` | `int32_t` | - | ID of node at side 2 | ✔ | ✔ | ❌ | ❌ | a valid node id | +| `node_3` | `int32_t` | - | ID of node at side 3 | ✔ | ✔ | ❌ | ❌ | a valid node id | +| `status_1` | `int8_t` | - | connection status at side 1 | ✔ | ✔ | ✔ | ❌ | `0` or `1` | +| `status_2` | `int8_t` | - | connection status at side 2 | ✔ | ✔ | ✔ | ❌ | `0` or `1` | +| `status_3` | `int8_t` | - | connection status at side 3 | ✔ | ✔ | ✔ | ❌ | `0` or `1` | +| `p_1` | `RealValueOutput` | watt (W) | active power flowing into the branch at side 1 | ✔ | ❌ | ❌ | ✔ | | +| `q_1` | `RealValueOutput` | volt-ampere-reactive (var) | reactive power flowing into the branch at side 1 | ✔ | ❌ | ❌ | ✔ | | +| `i_1` | `RealValueOutput` | ampere (A) | current at side 1 | ✔ | ❌ | ❌ | ✔ | | +| `s_1` | `RealValueOutput` | volt-ampere (VA) | apparent power flowing at side 1 | ✔ | ❌ | ❌ | ✔ | | +| `p_2` | `RealValueOutput` | watt (W) | active power flowing into the branch at side 2 | ✔ | ❌ | ❌ | ✔ | | +| `q_2` | `RealValueOutput` | volt-ampere-reactive (var) | reactive power flowing into the branch at side 2 | ✔ | ❌ | ❌ | ✔ | | +| `i_2` | `RealValueOutput` | ampere (A) | current at side 2 | ✔ | ❌ | ❌ | ✔ | | +| `s_2` | `RealValueOutput` | volt-ampere (VA) | apparent power flowing at side 2 | ✔ | ❌ | ❌ | ✔ | | +| `p_3` | `RealValueOutput` | watt (W) | active power flowing into the branch at side 3 | ✔ | ❌ | ❌ | ✔ | | +| `q_3` | `RealValueOutput` | volt-ampere-reactive (var) | reactive power flowing into the branch at side 3 | ✔ | ❌ | ❌ | ✔ | | +| `i_3` | `RealValueOutput` | ampere (A) | current at side 3 | ✔ | ❌ | ❌ | ✔ | | +| `s_3` | `RealValueOutput` | volt-ampere (VA) | apparent power flowing at side 3 | ✔ | ❌ | ❌ | ✔ | | +| `loading` | `double` | - | relative loading of the branch, `1.0` meaning 100% loaded. | ✔ | ❌ | ❌ | ✔ | | + +### Three-Winding Transformer + +`three_winding_transformer` connects three nodes with possibly different voltage levels. + +**Note: it can happen that `tap_min > tap_max`. In this case the winding voltage is decreased if the tap position is +increased.** + +| name | data type | unit | description | required | input | update | output | valid values | +|-------------| --- | --- |-----------------------------------------------------------------------------------------------------------|:--------------------------------:| :---: | :---: | :---: |:------------------------------------------------:| +| `u1` | `double` | volt (V) | rated voltage at side 1 | ✔ | ✔ | ❌ | ❌ | `> 0` | +| `u2` | `double` | volt (V) | rated voltage at side 2 | ✔ | ✔ | ❌ | ❌ | `> 0` | +| `u3` | `double` | volt (V) | rated voltage at side 3 | ✔ | ✔ | ❌ | ❌ | `> 0` | +| `sn_1` | `double` | volt-ampere (VA) | rated power at side 1 | ✔ | ✔ | ❌ | ❌ | `> 0` | +| `sn_2` | `double` | volt-ampere (VA) | rated power at side 2 | ✔ | ✔ | ❌ | ❌ | `> 0` | +| `sn_3` | `double` | volt-ampere (VA) | rated power at side 3 | ✔ | ✔ | ❌ | ❌ | `> 0` | +| `uk_12` | `double` | - | relative short circuit voltage across side 1-2, `0.1` means 10% | ✔ | ✔ | ❌ | ❌ | `>= pk_12 / min(sn_1, sn_2)` and `> 0` and `< 1` | +| `uk_13` | `double` | - | relative short circuit voltage across side 1-3, `0.1` means 10% | ✔ | ✔ | ❌ | ❌ | `>= pk_13 / min(sn_1, sn_3)` and `> 0` and `< 1` | +| `uk_23` | `double` | - | relative short circuit voltage across side 2-3, `0.1` means 10% | ✔ | ✔ | ❌ | ❌ | `>= pk_23 / min(sn_2, sn_3)` and `> 0` and `< 1` | +| `pk_12` | `double` | watt (W) | short circuit (copper) loss across side 1-2 | ✔ | ✔ | ❌ | ❌ | `>= 0` | +| `pk_13` | `double` | watt (W) | short circuit (copper) loss across side 1-3 | ✔ | ✔ | ❌ | ❌ | `>= 0` | +| `pk_23` | `double` | watt (W) | short circuit (copper) loss across side 2-3 | ✔ | ✔ | ❌ | ❌ | `>= 0` | +| `i0` | `double` | - | relative no-load current with respect to side 1 | ✔ | ✔ | ❌ | ❌ | `>= p0 / sn` and `< 1` | +| `p0` | `double` | watt (W) | no-load (iron) loss | ✔ | ✔ | ❌ | ❌ | `>= 0` | +| `winding_1` | `WindingType` | - | side 1 winding type | ✔ | ✔ | ❌ | ❌ | | +| `winding_2` | `WindingType` | - | side 2 winding type | ✔ | ✔ | ❌ | ❌ | | +| `winding_3` | `WindingType` | - | side 3 winding type | ✔ | ✔ | ❌ | ❌ | | +| `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. | ✔ | ✔ | ❌ | ❌ | `>= 0` and `<= 12` | +| `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. | ✔ | ✔ | ❌ | ❌ | `>= 0` and `<= 12` | +| `tap_side` | `BranchSide` | - | side of tap changer | ✔ | ✔ | ❌ | ❌ | `side_1` or `side_2` or `side_3` | +| `tap_pos` | `int8_t` | - | current position of tap changer | ✔ | ✔ | ✔ | ❌ | `(tap_min <= tap_pos <= tap_max)` or `(tap_min >= tap_pos >= tap_max)` | +| `tap_min` | `int8_t` | - | position of tap changer at minimum voltage | ✔ | ✔ | ❌ | ❌ | | +| `tap_max` | `int8_t` | - | position of tap changer at maximum voltage | ✔ | ✔ | ❌ | ❌ | | +| `tap_nom` | `int8_t` | - | nominal position of tap changer | ❌ default zero | ✔ | ❌ | ❌ | `(tap_min <= tap_nom <= tap_max)` or `(tap_min >= tap_nom >= tap_max)` | +| `tap_size` | `double` | volt (V) | size of each tap of the tap changer | ✔ | ✔ | ❌ | ❌ | `> 0` | +| `uk_12_min` | `double` | - | relative short circuit voltage at minimum tap, across side 1-2 | ❌ default same as `uk_12` | ✔ | ❌ | ❌ | `>= pk_12_min / min(sn_1, sn_2)` and `> 0` and `< 1` | +| `uk_12_max` | `double` | - | relative short circuit voltage at maximum tap, across side 1-2 | ❌ default same as `uk_12` | ✔ | ❌ | ❌ | `>= pk_12_max / min(sn_1, sn_2)` and `> 0` and `< 1` | +| `pk_12_min` | `double` | watt (W) | short circuit (copper) loss at minimum tap, across side 1-2 | ❌ default same as `pk_12` | ✔ | ❌ | ❌ | `>= 0` | +| `pk_12_max` | `double` | watt (W) | short circuit (copper) loss at maximum tap, across side 1-2 | ❌ default same as `pk_12` | ✔ | ❌ | ❌ | `>= 0` | +| `uk_13_min` | `double` | - | relative short circuit voltage at minimum tap, across side 1-3 | ❌ default same as `uk_13` | ✔ | ❌ | ❌ | `>= pk_13_min / min(sn_1, sn_3)` and `> 0` and `< 1` | +| `uk_13_max` | `double` | - | relative short circuit voltage at maximum tap, across side 1-3 | ❌ default same as `uk_13` | ✔ | ❌ | ❌ | `>= pk_13_max / min(sn_1, sn_3)` and `> 0` and `< 1` | +| `pk_13_min` | `double` | watt (W) | short circuit (copper) loss at minimum tap, across side 1-3 | ❌ default same as `pk_13` | ✔ | ❌ | ❌ | `>= 0` | +| `pk_13_max` | `double` | watt (W) | short circuit (copper) loss at maximum tap, across side 1-3 | ❌ default same as `pk_13` | ✔ | ❌ | ❌ | `>= 0` | +| `uk_23_min` | `double` | - | relative short circuit voltage at minimum tap, across side 2-3 | ❌ default same as `uk_23` | ✔ | ❌ | ❌ | `>= pk_23_min / min(sn_2, sn_3)` and `> 0` and `< 1` | +| `uk_23_max` | `double` | - | relative short circuit voltage at maximum tap, across side 2-3 | ❌ default same as `uk_23` | ✔ | ❌ | ❌ | `>= pk_23_max / min(sn_2, sn_3)` and `> 0` and `< 1` | +| `pk_23_min` | `double` | watt (W) | short circuit (copper) loss at minimum tap, across side 2-3 | ❌ default same as `pk_23` | ✔ | ❌ | ❌ | `>= 0` | +| `pk_23_max` | `double` | watt (W) | short circuit (copper) loss at maximum tap, across side 2-3 | ❌ default same as `pk_23` | ✔ | ❌ | ❌ | `>= 0` | +| `r_grounding_1` | `double` | ohm (Ω) | grounding resistance at side 1, if relevant | ❌ default zero | ✔ | ❌ | ❌ | | +| `x_grounding_1` | `double` | ohm (Ω) | grounding reactance at side 1, if relevant | ❌ default zero | ✔ | ❌ | ❌ | | +| `r_grounding_2` | `double` | ohm (Ω) | grounding resistance at side 2, if relevant | ❌ default zero | ✔ | ❌ | ❌ | | +| `x_grounding_2` | `double` | ohm (Ω) | grounding reactance at side 2, if relevant | ❌ default zero | ✔ | ❌ | ❌ | | +| `r_grounding_3` | `double` | ohm (Ω) | grounding resistance at side 3, if relevant | ❌ default zero | ✔ | ❌ | ❌ | | +| `x_grounding_3` | `double` | ohm (Ω) | grounding reactance at side 3, if relevant | ❌ default zero | ✔ | ❌ | ❌ | + ## Appliance * type name: `appliance` -`appliance` is an abstract user which is coupled to a `node`. -For each `appliance` a switch is defined between the `appliance` and the `node`. +`appliance` is an abstract user which is coupled to a `node`. For each `appliance` a switch is defined between +the `appliance` and the `node`. **The sign of active/reactive power of the appliance depends on the reference direction.** @@ -287,9 +365,8 @@ For each `appliance` a switch is defined between the `appliance` and the `node`. * reference direction: generator `source` is representing the external network with a -[Thévenin's equivalence](https://en.wikipedia.org/wiki/Th%C3%A9venin%27s_theorem). -It has an infinite voltage source with an internal impedance. -The impedance is specified by convention as short circuit power. +[Thévenin's equivalence](https://en.wikipedia.org/wiki/Th%C3%A9venin%27s_theorem). It has an infinite voltage source +with an internal impedance. The impedance is specified by convention as short circuit power. | name | data type | unit | description | required | input | update | output | valid values | |---------------| --- | --- |----------------------------------------------------|:----------------------------:| :---: | :---: | :---: |:------------:| @@ -303,8 +380,8 @@ The impedance is specified by convention as short circuit power. * type name: `generic_load_gen` -`generic_load_gen` is an abstract load/generation -which contains only the type of the load/generation with response to voltage. +`generic_load_gen` is an abstract load/generation which contains only the type of the load/generation with response to +voltage. | name | data type | unit | description | required | input | update | output | | --- | --- | --- | --- | :---: | :---: | :---: | :---: | @@ -329,14 +406,13 @@ The table below shows a list of attributes. | `p_specified` | `RealValueInput` | watt (W) | specified active power | ✨ only for power flow | ✔ | ✔ | ❌ | | `q_specified` | `RealValueInput` | volt-ampere-reactive (var) | specified reactive power | ✨ only for power flow | ✔ | ✔ | ❌ | - ### Shunt * type name: `shunt` * reference direction: load -`shunt` is an `appliance` with a fixed admittance (impedance). -It behaves similar to a load/generator with type `const_impedance`. +`shunt` is an `appliance` with a fixed admittance (impedance). It behaves similar to a load/generator with +type `const_impedance`. | name | data type | unit | description | required | input | update | output | | --- | --- | --- | --- | :---: | :---: | :---: | :---: | @@ -345,14 +421,13 @@ It behaves similar to a load/generator with type `const_impedance`. | `g0` | `double` | siemens (S) | zero-sequence shunt conductance | ✨ only for asymmetric calculation | ✔ | ❌ | ❌ | | `b0` | `double` | siemens (S) | zero-sequence shunt susceptance | ✨ only for asymmetric calculation | ✔ | ❌ | ❌ | - ## Sensor * type name: `sensor` -`sensor` is an abstract type for all the sensor types. -A sensor does not have any physical meaning. Rather, it provides measurement data for the state estimation algorithm. -The state estimator uses the data to evaluate the state of the grid with the highest probability. +`sensor` is an abstract type for all the sensor types. A sensor does not have any physical meaning. Rather, it provides +measurement data for the state estimation algorithm. The state estimator uses the data to evaluate the state of the grid +with the highest probability. | name | data type | unit | description | required | input | update | output | valid values | | --- | --- | --- | --- | :---: | :---: | :---: | :---: | :---: | @@ -362,19 +437,18 @@ The state estimator uses the data to evaluate the state of the grid with the hig * type name: `generic_voltage_sensor` -`generic_voltage_sensor` is an abstract class for symmetric and asymmetric voltage sensor. -It measures the magnitude and (optionally) the angle of the voltage of a `node`. +`generic_voltage_sensor` is an abstract class for symmetric and asymmetric voltage sensor. It measures the magnitude +and (optionally) the angle of the voltage of a `node`. | name | data type | unit | description | required | input | update | output | valid values | | --- | --- | --- | --- | :---: | :---: | :---: | :---: | :---: | | `u_sigma` | `double` | volt (V) | standard deviation of the measurement error. Usually this is the absolute measurement error range divided by 3. | ✨ only for state estimation | ✔ | ✔ | ❌ | `> 0` | - #### Voltage Sensor Concrete Types There are two concrete types of voltage sensor. They share similar attributes: -the meaning of `RealValueInput` is different, as shown in the table below. In a `sym_voltage_sensor` the measured voltage is a line-to-line voltage. -In a `asym_voltage_sensor` the measured voltage is a 3-phase line-to-ground voltage. +the meaning of `RealValueInput` is different, as shown in the table below. In a `sym_voltage_sensor` the measured +voltage is a line-to-line voltage. In a `asym_voltage_sensor` the measured voltage is a 3-phase line-to-ground voltage. | type name | meaning of `RealValueInput` | | --- | --- | @@ -390,25 +464,20 @@ The table below shows a list of attributes. | `u_residual` | `RealValueOutput` | volt (V) | residual value between measured voltage magnitude and calculated voltage magnitude | | ❌ | ❌ | ✔ | | | `u_angle_residual` | `RealValueOutput` | rad | residual value between measured voltage angle and calculated voltage angle (only possible with phasor measurement units) | | ❌ | ❌ | ✔ | | - ### Generic Power Sensor * type name: `generic_power_sensor` -`power_sensor` is an abstract class for symmetric and asymmetric power sensor. -It measures the active/reactive power flow of a terminal. -The terminal is either connecting an `appliance` and a `node`, -or connecting the from/to end of a `branch` and a `node`. -In case of a terminal between an `appliance` and a `node`, -the power reference direction in the measurement data is the same as the reference direction of the `appliance`. -For example, if a `power_sensor` is measuring a `source`, -a positive `p_measured` indicates that the active power flows from the source to the node. - -| name | data type | unit | description | required | input | update | output | valid values | -| --- | --- | --- | --- | :---: | :---: | :---: | :---: | :---: | -| `measured_terminal_type` | `MeasuredTerminalType` | - | indicate if it measures an `appliance` or a `branch`| ✔ | ✔ | ❌ | ❌ | | -| `power_sigma` | `double` | volt-ampere (VA) | standard deviation of the measurement error. Usually this is the absolute measurement error range divided by 3. | ✨ only for state estimation| ✔ | ✔ | ❌ | `> 0` | +`power_sensor` is an abstract class for symmetric and asymmetric power sensor. It measures the active/reactive power +flow of a terminal. The terminal is either connecting an `appliance` and a `node`, or connecting the from/to end of +a `branch` and a `node`. In case of a terminal between an `appliance` and a `node`, the power reference direction in the +measurement data is the same as the reference direction of the `appliance`. For example, if a `power_sensor` is +measuring a `source`, a positive `p_measured` indicates that the active power flows from the source to the node. +| name | data type | unit | description | required | input | update | output | valid values | +| --- | --- | --- | --- | :---: | :---: | :---: | :---: |:---------------------------------------------------:| +| `measured_terminal_type` | `MeasuredTerminalType` | - | indicate if it measures an `appliance` or a `branch`| ✔ | ✔ | ❌ | ❌ | the terminal type should match the `measured_object` | +| `power_sigma` | `double` | volt-ampere (VA) | standard deviation of the measurement error. Usually this is the absolute measurement error range divided by 3. | ✨ only for state estimation| ✔ | ✔ | ❌ | `> 0` | #### Power Sensor Concrete Types @@ -422,7 +491,6 @@ the meaning of `RealValueInput` is different, as shown in the table below. The table below shows a list of attributes. - | name | data type | unit | description | required | input | update | output | | --- | --- | --- | --- | :---: | :---: | :---: | :---: | | `p_measured` | `RealValueInput` | watt (W) | measured active power | ✨ only for state estimation | ✔ | ✔ | ❌ | @@ -430,10 +498,10 @@ The table below shows a list of attributes. | `p_residual` | `RealValueOutput` | watt (W) | residual value between measured active power and calculated active power | | ❌ | ❌ | ✔ | | `q_residual` | `RealValueOutput` | volt-ampere-reactive (var) | residual value between measured reactive power and calculated reactive power | | ❌ | ❌ | ✔ | - # Selection of calculation method -There are four power-flow algorithms and one state estimation algorithm available in power-grid-model. Some methods use a prefactorization feature which is also described in this section. +There are four power-flow algorithms and one state estimation algorithm available in power-grid-model. Some methods use +a prefactorization feature which is also described in this section. ## Power-flow algorithms @@ -442,24 +510,37 @@ Following are guidelines to be considered while selecting a power-flow `Calculat ### Iterative methods These should be selected when exact solution is required within specified `error_tolerance`. -* + +* * `CalculationMethod.newton_raphson`: Traditional Newton-Raphson method. -* `CalculationMethod.iterative_current`: Newton-Raphson would be more robust in achieving convergence and require less iterations. However, Iterative current can be faster most times because it uses matrix prefactorization. - +* `CalculationMethod.iterative_current`: Newton-Raphson would be more robust in achieving convergence and require less + iterations. However, Iterative current can be faster most times because it uses matrix prefactorization. + ### Linear methods -Linear approximation methods are many times faster than the iterative methods. Can be used where approximate solutions are acceptable. Both methods have equal computation time for a single powerflow calculation. +Linear approximation methods are many times faster than the iterative methods. Can be used where approximate solutions +are acceptable. Both methods have equal computation time for a single powerflow calculation. + * `CalculationMethod.linear`: It will be more accurate when most of the load/generation types are of constant impedance. -* `CalculationMethod.linear_current`: It will be more accurate when most of the load/generation types are constant power or constant current. Batch calculations will be faster because matrix prefacorization is possible. +* `CalculationMethod.linear_current`: It will be more accurate when most of the load/generation types are constant power + or constant current. Batch calculations will be faster because matrix prefacorization is possible. ## State Estimation algorithms Following are guidelines to be considered while selecting a state estimation `CalculationMethod` for your application: -* `CalculationMethod.iterative_linear`: It is an iterative method which converges to a true solution. Matrix prefactorization is possible. +* `CalculationMethod.iterative_linear`: It is an iterative method which converges to a true solution. Matrix + prefactorization is possible. ## Matrix Prefactorization -Every iteration of power-flow or state estimation has a step of solving large number of sparse linear equations i.e. `AX=b` in matrix form. Computation wise this is a very expensive step. One major component of this step is factorization of the `A` matrix. In certain calculation methods, this `A` matrix and its factorization remains unchanged over iterations and batches (only specific cases) which makes it possible reuse the factorization, skip this step and improve performance. +Every iteration of power-flow or state estimation has a step of solving large number of sparse linear equations +i.e. `AX=b` in matrix form. Computation wise this is a very expensive step. One major component of this step is +factorization of the `A` matrix. In certain calculation methods, this `A` matrix and its factorization remains unchanged +over iterations and batches (only specific cases) which makes it possible reuse the factorization, skip this step and +improve performance. + +**Note:** Prefactorization over batches is possible when switching status or specified power values of load/generation +or source reference voltage is modified. It is not possible when topology or grid parameters are modified, i.e. in +switching of branches, shunt, sources or change in transformer tap positions. -**Note:** Prefactorization over batches is possible when switching status or specified power values of load/generation or source reference voltage is modified. It is not possible when topology or grid parameters are modified, i.e. in switching of branches, shunt, sources or change in transformer tap positions. diff --git a/include/power_grid_model/all_components.hpp b/include/power_grid_model/all_components.hpp index d3498b3796..b0e70c707f 100644 --- a/include/power_grid_model/all_components.hpp +++ b/include/power_grid_model/all_components.hpp @@ -18,13 +18,15 @@ #include "component/sensor.hpp" #include "component/shunt.hpp" #include "component/source.hpp" +#include "component/three_winding_transformer.hpp" #include "component/transformer.hpp" #include "component/voltage_sensor.hpp" namespace power_grid_model { -using AllComponents = ComponentList; +using AllComponents = + ComponentList; } // namespace power_grid_model diff --git a/include/power_grid_model/auxiliary/input.hpp b/include/power_grid_model/auxiliary/input.hpp index 982ad8eea6..c72a7f434c 100644 --- a/include/power_grid_model/auxiliary/input.hpp +++ b/include/power_grid_model/auxiliary/input.hpp @@ -24,6 +24,9 @@ POWER_GRID_MODEL_DATA_STRUCT_DEF(NodeInput, 1, BaseInput, POWER_GRID_MODEL_DATA_STRUCT_DEF(BranchInput, 1, BaseInput, ID, from_node, ID, to_node, IntS, from_status, IntS, to_status); +POWER_GRID_MODEL_DATA_STRUCT_DEF(Branch3Input, 1, BaseInput, + ID, node_1, ID, node_2, ID, node_3, IntS, status_1, IntS, status_2, IntS, status_3); + POWER_GRID_MODEL_DATA_STRUCT_DEF(ApplianceInput, 1, BaseInput, ID, node, IntS, status); @@ -42,6 +45,21 @@ POWER_GRID_MODEL_DATA_STRUCT_DEF(TransformerInput, 1, BranchInput, double, uk_min, double, uk_max, double, pk_min, double, pk_max, double, r_grounding_from, double, x_grounding_from, double, r_grounding_to, double, x_grounding_to); +// Due to the maximum entries in the POWER_GRID_MODEL_DATA_STRUCT_DEF macro, ThreeWindingTransformerInput is created in two steps +// TODO: generate input data in a different way +POWER_GRID_MODEL_DATA_STRUCT_DEF(ThreeWindingTransformerInputBasics, 1, Branch3Input, + double, u1, double, u2, double, u3, double, sn_1, double, sn_2, double, sn_3, + double, uk_12, double, uk_13, double, uk_23, double, pk_12, double, pk_13, double, pk_23, double, i0, double, p0, + WindingType, winding_1, WindingType, winding_2, WindingType, winding_3, + IntS, clock_12, IntS, clock_13, + Branch3Side, tap_side, IntS, tap_pos, IntS, tap_min, IntS, tap_max, IntS, tap_nom, double, tap_size); + +POWER_GRID_MODEL_DATA_STRUCT_DEF(ThreeWindingTransformerInput, 1, ThreeWindingTransformerInputBasics, + double, uk_12_min, double, uk_12_max, double, uk_13_min, double, uk_13_max, double, uk_23_min, double, uk_23_max, + double, pk_12_min, double, pk_12_max, double, pk_13_min, double, pk_13_max, double, pk_23_min, double, pk_23_max, + double, r_grounding_1, double, x_grounding_1, double, r_grounding_2, double, x_grounding_2, + double, r_grounding_3, double, x_grounding_3); + POWER_GRID_MODEL_DATA_STRUCT_DEF(GenericLoadGenInput, 1, ApplianceInput, LoadGenType, type); @@ -96,9 +114,15 @@ using BaseUpdate = BaseInput; POWER_GRID_MODEL_DATA_STRUCT_DEF(BranchUpdate, 1, BaseUpdate, IntS, from_status, IntS, to_status); +POWER_GRID_MODEL_DATA_STRUCT_DEF(Branch3Update, 1, BaseUpdate, + IntS, status_1, IntS, status_2, IntS, status_3); + POWER_GRID_MODEL_DATA_STRUCT_DEF(TransformerUpdate, 1, BranchUpdate, IntS, tap_pos); +POWER_GRID_MODEL_DATA_STRUCT_DEF(ThreeWindingTransformerUpdate, 1, Branch3Update, + IntS, tap_pos); + POWER_GRID_MODEL_DATA_STRUCT_DEF(ApplianceUpdate, 1, BaseUpdate, IntS, status); diff --git a/include/power_grid_model/auxiliary/output.hpp b/include/power_grid_model/auxiliary/output.hpp index 062645505a..4364857977 100644 --- a/include/power_grid_model/auxiliary/output.hpp +++ b/include/power_grid_model/auxiliary/output.hpp @@ -29,6 +29,13 @@ POWER_GRID_MODEL_DATA_STRUCT_DEF(BranchOutput, 1, BaseOutput, RealValue, p_from, RealValue, q_from, RealValue, i_from, RealValue, s_from, RealValue, p_to, RealValue, q_to, RealValue, i_to, RealValue, s_to); +template +POWER_GRID_MODEL_DATA_STRUCT_DEF(Branch3Output, 1, BaseOutput, + double, loading, + RealValue, p_1, RealValue, q_1, RealValue, i_1, RealValue, s_1, + RealValue, p_2, RealValue, q_2, RealValue, i_2, RealValue, s_2, + RealValue, p_3, RealValue, q_3, RealValue, i_3, RealValue, s_3); + template POWER_GRID_MODEL_DATA_STRUCT_DEF(ApplianceOutput, 1, BaseOutput, RealValue, p, RealValue, q, RealValue, i, RealValue, s, RealValue, pf); diff --git a/include/power_grid_model/component/branch3.hpp b/include/power_grid_model/component/branch3.hpp new file mode 100644 index 0000000000..5b53a23fb7 --- /dev/null +++ b/include/power_grid_model/component/branch3.hpp @@ -0,0 +1,164 @@ +// SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project +// +// SPDX-License-Identifier: MPL-2.0 + +#pragma once +#ifndef POWER_GRID_MODEL_COMPONENT_BRANCH3_HPP +#define POWER_GRID_MODEL_COMPONENT_BRANCH3_HPP + +#include "../auxiliary/input.hpp" +#include "../auxiliary/output.hpp" +#include "../calculation_parameters.hpp" +#include "base.hpp" + +namespace power_grid_model { + +class Branch3 : public Base { + public: + using InputType = Branch3Input; + using UpdateType = Branch3Update; + template + using OutputType = Branch3Output; + static constexpr char const* name = "branch3"; + ComponentType math_model_type() const final { + return ComponentType::branch3; + } + + Branch3(Branch3Input const& branch3_input) + : Base{branch3_input}, + node_1_{branch3_input.node_1}, + node_2_{branch3_input.node_2}, + node_3_{branch3_input.node_3}, + status_1_{(bool)branch3_input.status_1}, + status_2_{(bool)branch3_input.status_2}, + status_3_{(bool)branch3_input.status_3} { + if (node_1_ == node_2_ || node_1_ == node_3_ || node_2_ == node_3_) { + throw InvalidBranch3{id(), node_1_, node_2_, node_3_}; + } + } + + // getter + ID node_1() const { + return node_1_; + } + ID node_2() const { + return node_2_; + } + ID node_3() const { + return node_3_; + } + bool status_1() const { + return status_1_; + } + bool status_2() const { + return status_2_; + } + bool status_3() const { + return status_3_; + } + bool branch3_status() const { + return status_1_ && status_2_ && status_3_; // TODO: check if this makes sense for branch3 + } + + // virtual getter + bool energized(bool is_connected_to_source = true) const final { + return is_connected_to_source && (status_1_ || status_2_ || status_3_); + } + virtual double base_i_1() const = 0; + virtual double base_i_2() const = 0; + virtual double base_i_3() const = 0; + virtual double loading(double s_1, double s_2, double s_3) const = 0; + virtual std::array phase_shift() const = 0; + + template + std::array, 3> calc_param(bool is_connected_to_source = true) const { + if (!energized(is_connected_to_source)) { + return std::array, 3>{}; + } + if constexpr (sym) { + return sym_calc_param(); + } + else { + return asym_calc_param(); + } + } + + template + Branch3Output get_output(BranchMathOutput const& branch_math_output1, + BranchMathOutput const& branch_math_output2, + BranchMathOutput const& branch_math_output3) const { + // result object + Branch3Output output{}; + static_cast(output) = base_output(true); + // calculate result + output.p_1 = base_power * real(branch_math_output1.s_f); + output.q_1 = base_power * imag(branch_math_output1.s_f); + output.i_1 = base_i_1() * cabs(branch_math_output1.i_f); + output.s_1 = base_power * cabs(branch_math_output1.s_f); + + output.p_2 = base_power * real(branch_math_output2.s_f); + output.q_2 = base_power * imag(branch_math_output2.s_f); + output.i_2 = base_i_2() * cabs(branch_math_output2.i_f); + output.s_2 = base_power * cabs(branch_math_output2.s_f); + + output.p_3 = base_power * real(branch_math_output3.s_f); + output.q_3 = base_power * imag(branch_math_output3.s_f); + output.i_3 = base_i_3() * cabs(branch_math_output3.i_f); + output.s_3 = base_power * cabs(branch_math_output3.s_f); + + output.loading = loading(sum_val(output.s_1), sum_val(output.s_2), sum_val(output.s_3)); + + return output; + } + + template + Branch3Output get_null_output() const { + Branch3Output output{}; + static_cast(output) = base_output(false); + return output; + } + + // setter + bool set_status(IntS new_status_1, IntS new_status_2, IntS new_status_3) { + bool const set_1 = new_status_1 != na_IntS; + bool const set_2 = new_status_2 != na_IntS; + bool const set_3 = new_status_3 != na_IntS; + bool changed = false; + if (set_1) { + changed = changed || (status_1_ != (bool)new_status_1); + status_1_ = (bool)new_status_1; + } + if (set_2) { + changed = changed || (status_2_ != (bool)new_status_2); + status_2_ = (bool)new_status_2; + } + if (set_3) { + changed = changed || (status_3_ != (bool)new_status_3); + status_3_ = (bool)new_status_3; + } + return changed; + } + + // default update for branch3, will be hidden for three winding transformer + UpdateChange update(Branch3Update const& update) { + assert(update.id == id()); + bool const changed = set_status(update.status_1, update.status_2, update.status_3); + // change in branch3 connection will change both topo and param + return {changed, changed}; + } + + private: + ID node_1_; + ID node_2_; + ID node_3_; + bool status_1_; + bool status_2_; + bool status_3_; + + virtual std::array, 3> sym_calc_param() const = 0; + virtual std::array, 3> asym_calc_param() const = 0; +}; + +} // namespace power_grid_model + +#endif \ No newline at end of file diff --git a/include/power_grid_model/component/three_winding_transformer.hpp b/include/power_grid_model/component/three_winding_transformer.hpp new file mode 100644 index 0000000000..718097aef7 --- /dev/null +++ b/include/power_grid_model/component/three_winding_transformer.hpp @@ -0,0 +1,389 @@ +// SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project +// +// SPDX-License-Identifier: MPL-2.0 + +#pragma once +#ifndef POWER_GRID_MODEL_COMPONENT_THREE_WINDING_TRANSFORMER_HPP +#define POWER_GRID_MODEL_COMPONENT_THREE_WINDING_TRANSFORMER_HPP + +#include "branch3.hpp" +#include "transformer.hpp" +#include "transformer_utils.hpp" + +namespace power_grid_model { + +class ThreeWindingTransformer : public Branch3 { + public: + using InputType = ThreeWindingTransformerInput; + using UpdateType = ThreeWindingTransformerUpdate; + static constexpr char const* name = "three_winding_transformer"; + + ThreeWindingTransformer(ThreeWindingTransformerInput const& three_winding_transformer_input, double u1_rated, + double u2_rated, double u3_rated) + : Branch3{three_winding_transformer_input}, + u1_{three_winding_transformer_input.u1}, + u2_{three_winding_transformer_input.u2}, + u3_{three_winding_transformer_input.u3}, + u1_rated_{u1_rated}, + u2_rated_{u2_rated}, + u3_rated_{u3_rated}, + sn_1_{three_winding_transformer_input.sn_1}, + sn_2_{three_winding_transformer_input.sn_2}, + sn_3_{three_winding_transformer_input.sn_3}, + uk_12_{three_winding_transformer_input.uk_12}, + uk_13_{three_winding_transformer_input.uk_13}, + uk_23_{three_winding_transformer_input.uk_23}, + pk_12_{three_winding_transformer_input.pk_12}, + pk_13_{three_winding_transformer_input.pk_13}, + pk_23_{three_winding_transformer_input.pk_23}, + i0_{three_winding_transformer_input.i0}, + p0_{three_winding_transformer_input.p0}, + winding_1_{three_winding_transformer_input.winding_1}, + winding_2_{three_winding_transformer_input.winding_2}, + winding_3_{three_winding_transformer_input.winding_3}, + clock_12_{three_winding_transformer_input.clock_12}, + clock_13_{three_winding_transformer_input.clock_13}, + tap_side_{three_winding_transformer_input.tap_side}, + tap_pos_{three_winding_transformer_input.tap_pos}, + tap_min_{three_winding_transformer_input.tap_min}, + tap_max_{three_winding_transformer_input.tap_max}, + tap_nom_{three_winding_transformer_input.tap_nom == na_IntS ? (IntS)0 + : three_winding_transformer_input.tap_nom}, + tap_direction_{tap_max_ > tap_min_ ? (IntS)1 : (IntS)-1}, + tap_size_{three_winding_transformer_input.tap_size}, + uk_12_min_{is_nan(three_winding_transformer_input.uk_12_min) ? uk_12_ + : three_winding_transformer_input.uk_12_min}, + uk_12_max_{is_nan(three_winding_transformer_input.uk_12_max) ? uk_12_ + : three_winding_transformer_input.uk_12_max}, + uk_13_min_{is_nan(three_winding_transformer_input.uk_13_min) ? uk_13_ + : three_winding_transformer_input.uk_13_min}, + uk_13_max_{is_nan(three_winding_transformer_input.uk_13_max) ? uk_13_ + : three_winding_transformer_input.uk_13_max}, + uk_23_min_{is_nan(three_winding_transformer_input.uk_23_min) ? uk_23_ + : three_winding_transformer_input.uk_23_min}, + uk_23_max_{is_nan(three_winding_transformer_input.uk_23_max) ? uk_23_ + : three_winding_transformer_input.uk_23_max}, + pk_12_min_{is_nan(three_winding_transformer_input.pk_12_min) ? pk_12_ + : three_winding_transformer_input.pk_12_min}, + pk_12_max_{is_nan(three_winding_transformer_input.pk_12_max) ? pk_12_ + : three_winding_transformer_input.pk_12_max}, + pk_13_min_{is_nan(three_winding_transformer_input.pk_13_min) ? pk_13_ + : three_winding_transformer_input.pk_13_min}, + pk_13_max_{is_nan(three_winding_transformer_input.pk_13_max) ? pk_13_ + : three_winding_transformer_input.pk_13_max}, + pk_23_min_{is_nan(three_winding_transformer_input.pk_23_min) ? pk_23_ + : three_winding_transformer_input.pk_23_min}, + pk_23_max_{is_nan(three_winding_transformer_input.pk_23_max) ? pk_23_ + : three_winding_transformer_input.pk_23_max}, + base_i_1_{base_power_3p / u1_rated / sqrt3}, + base_i_2_{base_power_3p / u2_rated / sqrt3}, + base_i_3_{base_power_3p / u3_rated / sqrt3}, + z_grounding_1_{three_winding_transformer_input.r_grounding_1, three_winding_transformer_input.x_grounding_1}, + z_grounding_2_{three_winding_transformer_input.r_grounding_2, three_winding_transformer_input.x_grounding_2}, + z_grounding_3_{three_winding_transformer_input.r_grounding_3, three_winding_transformer_input.x_grounding_3} { + // check clock number + bool const is_1_wye = winding_1_ == WindingType::wye || winding_1_ == WindingType::wye_n; + bool const is_2_wye = winding_2_ == WindingType::wye || winding_2_ == WindingType::wye_n; + bool const is_3_wye = winding_3_ == WindingType::wye || winding_3_ == WindingType::wye_n; + + // check clock 12 + if ( // clock should be between 0 and 12 + clock_12_ < 0 || clock_12_ > 12 || + // even number is not possible if one side is wye winding and the other side is not wye winding. + ((clock_12_ % 2) == 0 && (is_1_wye != is_2_wye)) || + // odd number is not possible, if both sides are wye winding or both sides are not wye winding. + ((clock_12_ % 2) == 1 && (is_1_wye == is_2_wye))) { + throw InvalidTransformerClock{id(), clock_12_}; + } + // check clock 13 + if ( // clock should be between 0 and 12 + clock_13_ < 0 || clock_13_ > 12 || + // even number is not possible if one side is wye winding and the other side is not wye winding. + ((clock_13_ % 2) == 0 && (is_1_wye != is_3_wye)) || + // odd number is not possible, if both sides are wye winding or both sides are not wye winding. + ((clock_13_ % 2) == 1 && (is_1_wye == is_3_wye))) { + throw InvalidTransformerClock{id(), clock_13_}; + } + + // set clock to zero if it is 12 + clock_12_ = clock_12_ % 12; + clock_13_ = clock_13_ % 12; + // check tap bounds + tap_pos_ = tap_limit(tap_pos_); + } + + // override getter + double base_i_1() const final { + return base_i_1_; + } + double base_i_2() const final { + return base_i_2_; + } + double base_i_3() const final { + return base_i_3_; + } + double loading(double s_1, double s_2, double s_3) const final { + return std::max(std::max(s_1 / sn_1_, s_2 / sn_2_), s_3 / sn_3_); + } + // 3-way branch, phase shift = phase_node_x - phase_internal_node + // the clock_12 and clock_13 is reverted + // because clock_12 is the phase shift node_1 - node_2 + // and the phase shift in the math model is node_x - node_internal + std::array phase_shift() const final { + return {0.0, -clock_12_ * deg_30, -clock_13_ * deg_30}; + } + + // setter + bool set_tap(IntS new_tap) { + if (new_tap == na_IntS || new_tap == tap_pos_) { + return false; + } + tap_pos_ = tap_limit(new_tap); + return true; + } + + UpdateChange update(ThreeWindingTransformerUpdate const& update) { + assert(update.id == id()); + bool topo_changed = set_status(update.status_1, update.status_2, update.status_3); + bool param_changed = set_tap(update.tap_pos) || topo_changed; + return {topo_changed, param_changed}; + } + + private: + // three winding transformer parameters + double u1_, u2_, u3_; + double u1_rated_, u2_rated_, u3_rated_; + double sn_1_, sn_2_, sn_3_; + double uk_12_, uk_13_, uk_23_, pk_12_, pk_13_, pk_23_, i0_, p0_; + WindingType winding_1_, winding_2_, winding_3_; + IntS clock_12_, clock_13_; + Branch3Side tap_side_; + IntS tap_pos_, tap_min_, tap_max_, tap_nom_, tap_direction_; + double tap_size_; + double uk_12_min_, uk_12_max_, uk_13_min_, uk_13_max_, uk_23_min_, uk_23_max_; + double pk_12_min_, pk_12_max_, pk_13_min_, pk_13_max_, pk_23_min_, pk_23_max_; + + // calculation parameters + double base_i_1_; + double base_i_2_; + double base_i_3_; + DoubleComplex z_grounding_1_; + DoubleComplex z_grounding_2_; + DoubleComplex z_grounding_3_; + + IntS tap_limit(IntS new_tap) const { + new_tap = std::min(new_tap, std::max(tap_max_, tap_min_)); + new_tap = std::max(new_tap, std::min(tap_max_, tap_min_)); + return new_tap; + } + + std::tuple calculate_uk() const { + // adjust uk for tap from min to max range + double const uk_12_tap = + tap_adjust_impedance(tap_pos_, tap_min_, tap_max_, tap_nom_, uk_12_, uk_12_min_, uk_12_max_); + double const uk_13_tap = + tap_adjust_impedance(tap_pos_, tap_min_, tap_max_, tap_nom_, uk_13_, uk_13_min_, uk_13_max_); + double const uk_23_tap = + tap_adjust_impedance(tap_pos_, tap_min_, tap_max_, tap_nom_, uk_23_, uk_23_min_, uk_23_max_); + + // convert all short circuit voltages relative to side 1 + double const uk_12 = uk_12_tap * sn_1_ / std::min(sn_1_, sn_2_); + double const uk_13 = uk_13_tap * sn_1_ / std::min(sn_1_, sn_3_); + double const uk_23 = uk_23_tap * sn_1_ / std::min(sn_2_, sn_3_); + + // delta-wye conversion (12, 13, 23 -> 1, 2, 3) + double const uk_T1_prime = 0.5 * (uk_12 + uk_13 - uk_23); + double const uk_T2_prime = 0.5 * (uk_12 + uk_23 - uk_13); + double const uk_T3_prime = 0.5 * (uk_13 + uk_23 - uk_12); + + // transform short circuit voltages back to their own voltage level + double const uk_T1 = uk_T1_prime; + double const uk_T2 = uk_T2_prime * (sn_2_ / sn_1_); + double const uk_T3 = uk_T3_prime * (sn_3_ / sn_1_); + return std::make_tuple(uk_T1, uk_T2, uk_T3); + } + + std::tuple calculate_pk() const { + // adjust pk for tap from min to max range + double const pk_12_tap = + tap_adjust_impedance(tap_pos_, tap_min_, tap_max_, tap_nom_, pk_12_, pk_12_min_, pk_12_max_); + double const pk_13_tap = + tap_adjust_impedance(tap_pos_, tap_min_, tap_max_, tap_nom_, pk_13_, pk_13_min_, pk_13_max_); + double const pk_23_tap = + tap_adjust_impedance(tap_pos_, tap_min_, tap_max_, tap_nom_, pk_23_, pk_23_min_, pk_23_max_); + + // convert all short circuit losses relative to side 1 + double const pk_12 = pk_12_tap * (sn_1_ / std::min(sn_1_, sn_2_)) * (sn_1_ / std::min(sn_1_, sn_2_)); + double const pk_13 = pk_13_tap * (sn_1_ / std::min(sn_1_, sn_3_)) * (sn_1_ / std::min(sn_1_, sn_3_)); + double const pk_23 = pk_23_tap * (sn_1_ / std::min(sn_2_, sn_3_)) * (sn_1_ / std::min(sn_2_, sn_3_)); + + // delta-wye conversion (12, 13, 23 -> 1, 2, 3) + double const pk_T1_prime = 0.5 * (pk_12 + pk_13 - pk_23); + double const pk_T2_prime = 0.5 * (pk_12 + pk_23 - pk_13); + double const pk_T3_prime = 0.5 * (pk_13 + pk_23 - pk_12); + + // transform short circuit losses back to their own power level + double const pk_T1 = pk_T1_prime; + double const pk_T2 = pk_T2_prime * (sn_2_ / sn_1_) * (sn_2_ / sn_1_); + double const pk_T3 = pk_T3_prime * (sn_3_ / sn_1_) * (sn_3_ / sn_1_); + return std::make_tuple(pk_T1, pk_T2, pk_T3); + } + + /* + A three winding transformer can be modelled as three two winding transformers, between the three nodes and the + dummy node: + - T1: node 1 -> dummy node + - T2: node 2 -> dummy node + - T3: node 3 -> dummy node + + The three two winding transformers look as follows: + + node_2 + / + T2 + / + node_1 -- T1 -- dummy_node + \ + T3 + \ + node_3 + + - Each two winding transformer has a dummy id (2) and dummy nodes (0 and 1). + - The from status is the actual status of the threewinding transformer with teh corresponding node, + the to status is always true. + - The voltage at the dummy node is the same as on node 1 + - i0 and p0 are only applicable to T1 + - T1 will always be YNyn0 transformer + - The voltage levels will be calculated in advance, so tap_pos/min/max/nom/size can all be set to zero + - uk and pk are calculated in advance, so uk_min/max and pk_min/max can be set to nan + */ + std::array convert_to_two_winding_transformers() const { + // off nominal tap ratio + auto const [u1, u2, u3] = [this]() { + double u1 = u1_, u2 = u2_, u3 = u3_; + if (tap_side_ == Branch3Side::side_1) { + u1 += tap_direction_ * (tap_pos_ - tap_nom_) * tap_size_; + } + else if (tap_side_ == Branch3Side::side_2) { + u2 += tap_direction_ * (tap_pos_ - tap_nom_) * tap_size_; + } + else { + u3 += tap_direction_ * (tap_pos_ - tap_nom_) * tap_size_; + } + return std::make_tuple(u1, u2, u3); + }(); + + auto const [uk_T1, uk_T2, uk_T3] = calculate_uk(); + auto const [pk_T1, pk_T2, pk_T3] = calculate_pk(); + + TransformerInput const transformer_input_T1{ + {{2}, 0, 1, status_1(), true}, // {{id}, from_node, to_node, from_status, to_status} + u1, // u1 + u1, // u2 + sn_1_, // sn + uk_T1, // uk + pk_T1, // pk + i0_, // i0 + p0_, // p0 + WindingType::wye_n, // winding_from + WindingType::wye_n, // winding_to + 0, // clock + BranchSide::from, // tap_side + 0, // tap_pos + 0, // tap_min + 0, // tap_max + 0, // tap_nom + 0.0, // tap_size + nan, // uk_min + nan, // uk_max + nan, // pk_min + nan, // pk_max + z_grounding_1_.real(), // r_grounding_from + z_grounding_1_.imag(), // x_grounding_from + 0, // r_grounding_to + 0 // x_grounding_to + }; + TransformerInput const transformer_input_T2{ + {{2}, 0, 1, status_2(), true}, // {{id}, from_node, to_node, from_status, to_status} + u2, // u1 + u1, // u2 + sn_2_, // sn + uk_T2, // uk + pk_T2, // pk + 0.0, // i0 + 0.0, // p0 + winding_2_, // winding_from + winding_1_, // winding_to + static_cast(12 - clock_12_), // clock, reversed + BranchSide::from, // tap_side + 0, // tap_pos + 0, // tap_min + 0, // tap_max + 0, // tap_nom + 0.0, // tap_size + nan, // uk_min + nan, // uk_max + nan, // pk_min + nan, // pk_max + z_grounding_2_.real(), // r_grounding_from + z_grounding_2_.imag(), // x_grounding_from + 0, // r_grounding_to + 0 // x_grounding_to + }; + TransformerInput const transformer_input_T3{ + {{2}, 0, 1, status_3(), true}, // {{id}, from_node, to_node, from_status, to_status} + u3, // u1 + u1, // u2 + sn_3_, // sn + uk_T3, // uk + pk_T3, // pk + 0.0, // i0 + 0.0, // p0 + winding_3_, // winding_from + winding_1_, // winding_to + static_cast(12 - clock_13_), // clock, reversed + BranchSide::from, // tap_side + 0, // tap_pos + 0, // tap_min + 0, // tap_max + 0, // tap_nom + 0.0, // tap_size + nan, // uk_min + nan, // uk_max + nan, // pk_min + nan, // pk_max + z_grounding_3_.real(), // r_grounding_from + z_grounding_3_.imag(), // x_grounding_from + 0, // r_grounding_to + 0 // x_grounding_to + }; + + Transformer const T1{transformer_input_T1, u1_rated_, u1_rated_}; + Transformer const T2{transformer_input_T2, u2_rated_, u1_rated_}; + Transformer const T3{transformer_input_T3, u3_rated_, u1_rated_}; + + return {T1, T2, T3}; + } + + // calculate branch parameters + std::array, 3> sym_calc_param() const final { + std::array const transformer_array = convert_to_two_winding_transformers(); + std::array, 3> transformer_params{}; + for (size_t i = 0; i < transformer_array.size(); i++) { + transformer_params[i] = transformer_array[i].calc_param(); + } + return transformer_params; + } + std::array, 3> asym_calc_param() const final { + std::array const transformer_array = convert_to_two_winding_transformers(); + std::array, 3> transformer_params{}; + for (size_t i = 0; i < transformer_array.size(); i++) { + transformer_params[i] = transformer_array[i].calc_param(); + } + return transformer_params; + } +}; + +} // namespace power_grid_model + +#endif \ No newline at end of file diff --git a/include/power_grid_model/component/transformer.hpp b/include/power_grid_model/component/transformer.hpp index a14cc49995..1d2372d791 100644 --- a/include/power_grid_model/component/transformer.hpp +++ b/include/power_grid_model/component/transformer.hpp @@ -13,6 +13,7 @@ #include "../power_grid_model.hpp" #include "../three_phase_tensor.hpp" #include "branch.hpp" +#include "transformer_utils.hpp" namespace power_grid_model { @@ -49,9 +50,9 @@ class Transformer : public Branch { base_i_to_{base_power_3p / u2_rated / sqrt3}, nominal_ratio_{u1_rated / u2_rated}, z_grounding_from_{ - get_z_grounding(transformer_input.r_grounding_from, transformer_input.x_grounding_from, u1_rated)}, + calculate_z_pu(transformer_input.r_grounding_from, transformer_input.x_grounding_from, u1_rated)}, z_grounding_to_{ - get_z_grounding(transformer_input.r_grounding_to, transformer_input.x_grounding_to, u2_rated)} { + calculate_z_pu(transformer_input.r_grounding_to, transformer_input.x_grounding_to, u2_rated)} { // check on clock bool const is_from_wye = winding_from_ == WindingType::wye || winding_from_ == WindingType::wye_n; bool const is_to_wye = winding_to_ == WindingType::wye || winding_to_ == WindingType::wye_n; @@ -103,7 +104,7 @@ class Transformer : public Branch { UpdateChange update(TransformerUpdate const& update) { assert(update.id == id()); bool topo_changed = set_status(update.from_status, update.to_status); - bool param_changed = set_tap(update.tap_pos); + bool param_changed = set_tap(update.tap_pos) || topo_changed; return {topo_changed, param_changed}; } @@ -126,8 +127,8 @@ class Transformer : public Branch { double nominal_ratio_; DoubleComplex z_grounding_from_, z_grounding_to_; - // calculate z grounding with NaN detection and per unit - DoubleComplex get_z_grounding(double r, double x, double u) { + // calculate z in per unit with NaN detection + DoubleComplex calculate_z_pu(double r, double x, double u) { r = is_nan(r) ? 0 : r; x = is_nan(x) ? 0 : x; double const base_z = u * u / base_power_3p; @@ -152,19 +153,27 @@ class Transformer : public Branch { else { u2 += tap_direction_ * (tap_pos_ - tap_nom_) * tap_size_; } - return std::pair{u1, u2}; + return std::pair{u1, u2}; }(); double const k = (u1 / u2) / nominal_ratio_; // pk and uk - double const pk = get_pk(), uk = get_uk(); + double const uk = tap_adjust_impedance(tap_pos_, tap_min_, tap_max_, tap_nom_, uk_, uk_min_, uk_max_); + double const pk = tap_adjust_impedance(tap_pos_, tap_min_, tap_max_, tap_nom_, pk_, pk_min_, pk_max_); + // series DoubleComplex z_series, y_series; - // Z = uk*U2^2/S - double const z_series_abs = uk * u2 * u2 / sn_; + // sign of uk + // uk can be negative for aritificual transformer from 3-winding + // in this case, the imaginary part of z_series should be negative + double const uk_sign = (uk >= 0) ? 1.0 : -1.0; + // Z = abs(uk)*U2^2/S + double const z_series_abs = cabs(uk) * u2 * u2 / sn_; // R = pk * U2^2/S^2 + // pk can be negative for aritificual transformer from 3-winding + // in this case, the real part of z_series should be negative z_series.real(pk * u2 * u2 / sn_ / sn_); - // X = sqrt(Z^2 - R^2) - z_series.imag(std::sqrt(z_series_abs * z_series_abs - z_series.real() * z_series.real())); + // X = uk_sign * sqrt(Z^2 - R^2) + z_series.imag(uk_sign * std::sqrt(z_series_abs * z_series_abs - z_series.real() * z_series.real())); // y series y_series = (1.0 / z_series) / base_y_to; // shunt @@ -185,54 +194,6 @@ class Transformer : public Branch { return std::make_tuple(y_series, y_shunt, k); } - double get_uk() const { - double uk_increment_per_tap{}; - double uk{}; - if (tap_pos_ <= std::max(tap_nom_, tap_max_) && tap_pos_ >= std::min(tap_nom_, tap_max_)) { - if (tap_max_ == tap_nom_) { - uk = uk_; - } - else { - uk_increment_per_tap = (uk_max_ - uk_) / (tap_max_ - tap_nom_); - uk = uk_ + (tap_pos_ - tap_nom_) * uk_increment_per_tap; - } - } - else { - if (tap_min_ == tap_nom_) { - uk = uk_; - } - else { - uk_increment_per_tap = (uk_min_ - uk_) / (tap_min_ - tap_nom_); - uk = uk_ + (tap_pos_ - tap_nom_) * uk_increment_per_tap; - } - } - return uk; - } - - double get_pk() const { - double pk_increment_per_tap{}; - double pk{}; - if (tap_pos_ <= std::max(tap_nom_, tap_max_) && tap_pos_ >= std::min(tap_nom_, tap_max_)) { - if (tap_max_ == tap_nom_) { - pk = pk_; - } - else { - pk_increment_per_tap = (pk_max_ - pk_) / (tap_max_ - tap_nom_); - pk = pk_ + (tap_pos_ - tap_nom_) * pk_increment_per_tap; - } - } - else { - if (tap_min_ == tap_nom_) { - pk = pk_; - } - else { - pk_increment_per_tap = (pk_min_ - pk_) / (tap_min_ - tap_nom_); - pk = pk_ + (tap_pos_ - tap_nom_) * pk_increment_per_tap; - } - } - return pk; - } - // branch param BranchCalcParam sym_calc_param() const final { auto const [y_series, y_shunt, k] = transformer_params(); diff --git a/include/power_grid_model/component/transformer_utils.hpp b/include/power_grid_model/component/transformer_utils.hpp new file mode 100644 index 0000000000..6245a2e4d9 --- /dev/null +++ b/include/power_grid_model/component/transformer_utils.hpp @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project +// +// SPDX-License-Identifier: MPL-2.0 + +#pragma once +#ifndef POWER_GRID_MODEL_COMPONENT_TRANSFORMER_UTILS_HPP +#define POWER_GRID_MODEL_COMPONENT_TRANSFORMER_UTILS_HPP + +inline double tap_adjust_impedance(double tap_pos, double tap_min, double tap_max, double tap_nom, double xk, + double xk_min, double xk_max) { + if (tap_pos <= std::max(tap_nom, tap_max) && tap_pos >= std::min(tap_nom, tap_max)) { + if (tap_max == tap_nom) { + return xk; + } + else { + double const xk_increment_per_tap = (xk_max - xk) / (tap_max - tap_nom); + return xk + (tap_pos - tap_nom) * xk_increment_per_tap; + } + } + else { + if (tap_min == tap_nom) { + return xk; + } + else { + double const xk_increment_per_tap = (xk_min - xk) / (tap_min - tap_nom); + return xk + (tap_pos - tap_nom) * xk_increment_per_tap; + } + } +} + +// add tap + +#endif diff --git a/include/power_grid_model/enum.hpp b/include/power_grid_model/enum.hpp index e822769c3a..d5d13ffa79 100644 --- a/include/power_grid_model/enum.hpp +++ b/include/power_grid_model/enum.hpp @@ -20,6 +20,8 @@ enum class WindingType : IntS { wye = 0, wye_n = 1, delta = 2, zigzag = 3, zigza enum class BranchSide : IntS { from = 0, to = 1 }; +enum class Branch3Side : IntS { side_1 = 0, side_2 = 1, side_3 = 2 }; + enum class CalculationMethod : IntS { linear = 0, newton_raphson = 1, @@ -34,7 +36,10 @@ enum class MeasuredTerminalType : IntS { source = 2, shunt = 3, load = 4, - generator = 5 + generator = 5, + branch3_1 = 6, + branch3_2 = 7, + branch3_3 = 8 }; enum class ComponentType : IntS { @@ -46,7 +51,8 @@ enum class ComponentType : IntS { generic_voltage_sensor = 5, generic_load_gen = 6, shunt = 7, - source = 8 + source = 8, + branch3 = 9 }; // DO NOT change the order of enumerations diff --git a/include/power_grid_model/exception.hpp b/include/power_grid_model/exception.hpp index 10978f3270..f96c6969be 100644 --- a/include/power_grid_model/exception.hpp +++ b/include/power_grid_model/exception.hpp @@ -51,6 +51,15 @@ class InvalidBranch : public PowerGridError { } }; +class InvalidBranch3 : public PowerGridError { + public: + InvalidBranch3(ID branch3_id, ID node_1_id, ID node_2_id, ID node_3_id) { + append_msg("Branch3 " + std::to_string(branch3_id) + + " is connected to the same node at least twice. Node 1/2/3: " + std::to_string(node_1_id) + "/" + + std::to_string(node_2_id) + "/" + std::to_string(node_3_id) + ",\n This is not allowed!\n"); + } +}; + class InvalidTransformerClock : public PowerGridError { public: InvalidTransformerClock(ID id, IntS clock) { diff --git a/include/power_grid_model/main_model.hpp b/include/power_grid_model/main_model.hpp index f5f55d6025..3f5a6bd66b 100644 --- a/include/power_grid_model/main_model.hpp +++ b/include/power_grid_model/main_model.hpp @@ -160,6 +160,12 @@ class MainModelImpl, ComponentLis components_.template emplace(id, input, u1, u2); } } + else if constexpr (std::is_base_of_v) { + double const u1 = components_.template get_item(input.node_1).u_rated(); + double const u2 = components_.template get_item(input.node_2).u_rated(); + double const u3 = components_.template get_item(input.node_3).u_rated(); + components_.template emplace(id, input, u1, u2, u3); + } else if constexpr (std::is_base_of_v) { double const u = components_.template get_item(input.node).u_rated(); components_.template emplace(id, input, u); @@ -181,6 +187,11 @@ class MainModelImpl, ComponentLis case MeasuredTerminalType::branch_to: components_.template get_item(measured_object); break; + case MeasuredTerminalType::branch3_1: + case MeasuredTerminalType::branch3_2: + case MeasuredTerminalType::branch3_3: + components_.template get_item(measured_object); + break; case MeasuredTerminalType::shunt: components_.template get_item(measured_object); break; @@ -285,7 +296,13 @@ class MainModelImpl, ComponentLis return BranchIdx{components_.template get_seq(branch.from_node()), components_.template get_seq(branch.to_node())}; }); - // skip branch3 + comp_topo.branch3_node_idx.resize(components_.template size()); + std::transform(components_.template citer().begin(), components_.template citer().end(), + comp_topo.branch3_node_idx.begin(), [this](Branch3 const& branch3) { + return Branch3Idx{components_.template get_seq(branch3.node_1()), + components_.template get_seq(branch3.node_2()), + components_.template get_seq(branch3.node_3())}; + }); comp_topo.source_node_idx.resize(components_.template size()); std::transform(components_.template citer().begin(), components_.template citer().end(), comp_topo.source_node_idx.begin(), [this](Source const& source) { @@ -329,6 +346,10 @@ class MainModelImpl, ComponentLis case MeasuredTerminalType::load: case MeasuredTerminalType::generator: return components_.template get_seq(power_sensor.measured_object()); + case MeasuredTerminalType::branch3_1: + case MeasuredTerminalType::branch3_2: + case MeasuredTerminalType::branch3_3: + return components_.template get_seq(power_sensor.measured_object()); default: throw MissingCaseForEnumError("Power sensor idx to seq transformation", power_sensor.get_terminal_type()); @@ -621,6 +642,12 @@ class MainModelImpl, ComponentLis return is_nan(update.from_status) && is_nan(update.to_status); }); } + else if constexpr (std::is_base_of_v) { + // Check for all batches + return std::all_of(it_begin, it_end, [](Branch3Update const& update) { + return is_nan(update.status_1) && is_nan(update.status_2) && is_nan(update.status_3); + }); + } else if constexpr (std::is_base_of_v) { // Check for all batches return std::all_of(it_begin, it_end, [](SourceUpdate const& update) { @@ -720,6 +747,28 @@ class MainModelImpl, ComponentLis }); } + // output branch3 + template + std::enable_if_t< + std::is_base_of_v::iterator_category> && + std::is_base_of_v, + ResIt> + output_result(std::vector> const& math_output, ResIt res_it) { + assert(construction_complete_); + return std::transform(components_.template citer().begin(), + components_.template citer().end(), + comp_coup_->branch3.cbegin() + components_.template get_start_idx(), + res_it, [&math_output](Branch3 const& branch3, Idx2DBranch3 math_id) { + if (math_id.group == -1) { + return branch3.get_null_output(); + } + + return branch3.get_output(math_output[math_id.group].branch[math_id.pos[0]], + math_output[math_id.group].branch[math_id.pos[1]], + math_output[math_id.group].branch[math_id.pos[2]]); + }); + } + // output source, loadgen, shunt individually template std::enable_if_t< @@ -824,7 +873,7 @@ class MainModelImpl, ComponentLis components_.template get_start_idx(), res_it, [this, &math_output](GenericPowerSensor const& power_sensor, Idx const obj_seq) { auto const terminal_type = power_sensor.get_terminal_type(); - const Idx2D obj_math_id = [&]() { + Idx2D const obj_math_id = [&]() { switch (terminal_type) { case MeasuredTerminalType::branch_from: case MeasuredTerminalType::branch_to: @@ -836,6 +885,13 @@ class MainModelImpl, ComponentLis case MeasuredTerminalType::load: case MeasuredTerminalType::generator: return comp_coup_->load_gen[obj_seq]; + // from branch3, get relevant math object branch based on the measured side + case MeasuredTerminalType::branch3_1: + return Idx2D{comp_coup_->branch3[obj_seq].group, comp_coup_->branch3[obj_seq].pos[0]}; + case MeasuredTerminalType::branch3_2: + return Idx2D{comp_coup_->branch3[obj_seq].group, comp_coup_->branch3[obj_seq].pos[1]}; + case MeasuredTerminalType::branch3_3: + return Idx2D{comp_coup_->branch3[obj_seq].group, comp_coup_->branch3[obj_seq].pos[2]}; default: throw MissingCaseForEnumError(std::string(GenericPowerSensor::name) + " output_result()", terminal_type); @@ -848,6 +904,10 @@ class MainModelImpl, ComponentLis switch (terminal_type) { case MeasuredTerminalType::branch_from: + // all power sensors in branch3 are at from side in the mathematical model + case MeasuredTerminalType::branch3_1: + case MeasuredTerminalType::branch3_2: + case MeasuredTerminalType::branch3_3: return power_sensor.get_output(math_output[obj_math_id.group].branch[obj_math_id.pos].s_f); case MeasuredTerminalType::branch_to: return power_sensor.get_output(math_output[obj_math_id.group].branch[obj_math_id.pos].s_t); @@ -948,7 +1008,14 @@ class MainModelImpl, ComponentLis comp_conn.branch_phase_shift.begin(), [](Branch const& branch) { return branch.phase_shift(); }); - // skip branch3 for now + std::transform(components_.template citer().begin(), components_.template citer().end(), + comp_conn.branch3_connected.begin(), [](Branch3 const& branch3) { + return Branch3Connected{branch3.status_1(), branch3.status_2(), branch3.status_3()}; + }); + std::transform(components_.template citer().begin(), components_.template citer().end(), + comp_conn.branch3_phase_shift.begin(), [](Branch3 const& branch3) { + return branch3.phase_shift(); + }); std::transform(components_.template citer().begin(), components_.template citer().end(), comp_conn.source_connected.begin(), [](Source const& source) { return source.status(); @@ -980,7 +1047,18 @@ class MainModelImpl, ComponentLis math_param[math_idx.group].branch_param[math_idx.pos] = components_.template get_item_by_seq(i).template calc_param(); } - // skip branch3 + // loop all branch3 + for (Idx i = 0; i != (Idx)comp_topo_->branch3_node_idx.size(); ++i) { + Idx2DBranch3 const math_idx = comp_coup_->branch3[i]; + if (math_idx.group == -1) { + continue; + } + // assign parameters, branch3 param consists of three branch parameters + auto const branch3_param = components_.template get_item_by_seq(i).template calc_param(); + for (size_t branch2 = 0; branch2 < 3; ++branch2) { + math_param[math_idx.group].branch_param[math_idx.pos[branch2]] = branch3_param[branch2]; + } + } // loop all shunt for (Idx i = 0; i != (Idx)comp_topo_->shunt_node_idx.size(); ++i) { Idx2D const math_idx = comp_coup_->shunt[i]; @@ -1152,7 +1230,11 @@ class MainModelImpl, ComponentLis prepare_input, SensorCalcParam, &StateEstimationInput::measured_branch_from_power, GenericPowerSensor>( comp_coup_->power_sensor, se_input, [&](Idx i) { - return comp_topo_->power_sensor_terminal_type[i] == MeasuredTerminalType::branch_from; + return comp_topo_->power_sensor_terminal_type[i] == MeasuredTerminalType::branch_from || + // all branch3 sensors are at from side in the mathemtical model + comp_topo_->power_sensor_terminal_type[i] == MeasuredTerminalType::branch3_1 || + comp_topo_->power_sensor_terminal_type[i] == MeasuredTerminalType::branch3_2 || + comp_topo_->power_sensor_terminal_type[i] == MeasuredTerminalType::branch3_3; }); prepare_input, SensorCalcParam, &StateEstimationInput::measured_branch_to_power, GenericPowerSensor>( @@ -1209,9 +1291,10 @@ class MainModelImpl, ComponentLis } }; -using MainModel = MainModelImpl, - AllComponents>; +using MainModel = + MainModelImpl, + AllComponents>; } // namespace power_grid_model diff --git a/include/power_grid_model/topology.hpp b/include/power_grid_model/topology.hpp index 25cccc5d55..156aadce64 100644 --- a/include/power_grid_model/topology.hpp +++ b/include/power_grid_model/topology.hpp @@ -488,16 +488,59 @@ class Topology { return true; }; + // proxy class to find the coupled object in math model in the coupling process to a single type object + // given a particular component index + struct SingleTypeObjectFinder { + Idx size() const { + return (Idx)component_obj_idx.size(); + } + Idx2D find_math_object(Idx component_i) const { + return objects_coupling[component_obj_idx[component_i]]; + } + IdxVector const& component_obj_idx; + std::vector const& objects_coupling; + }; + + // proxy class to find coupled branch in math model for sensor measured at from side, or at 1/2/3 side of branch3 + // they are all coupled to the from-side of some branches in math model + // the key is to find relevant coupling, either via branch or branch3 + struct SensorBranchObjectFinder { + Idx size() const { + return (Idx)sensor_obj_idx.size(); + } + Idx2D find_math_object(Idx component_i) const { + Idx const obj_idx = sensor_obj_idx[component_i]; + switch (power_sensor_terminal_type[component_i]) { + case MeasuredTerminalType::branch_from: + return branch_coupling[obj_idx]; + // return relevant branch mapped from branch3 + case MeasuredTerminalType::branch3_1: + return {branch3_coupling[obj_idx].group, branch3_coupling[obj_idx].pos[0]}; + case MeasuredTerminalType::branch3_2: + return {branch3_coupling[obj_idx].group, branch3_coupling[obj_idx].pos[1]}; + case MeasuredTerminalType::branch3_3: + return {branch3_coupling[obj_idx].group, branch3_coupling[obj_idx].pos[2]}; + default: + assert(false); + return {}; + } + } + IdxVector const& sensor_obj_idx; + std::vector const& power_sensor_terminal_type; + std::vector const& branch_coupling; + std::vector const& branch3_coupling; + }; + // Couple one type of components (e.g. appliances or sensors) // The indptr in math topology will be modified // The coupling element should be pre-allocated in coupling // Only connect the component if include(component_i) returns true template - void couple_object_components(IdxVector const& component_obj_idx, std::vector& objects, - std::vector& coupling, Predicate include = include_all) { + typename ObjectFinder = SingleTypeObjectFinder, typename Predicate = decltype(include_all)> + void couple_object_components(ObjectFinder object_finder, std::vector& coupling, + Predicate include = include_all) { auto const n_math_topologies((Idx)math_topology_.size()); - auto const n_components = (Idx)component_obj_idx.size(); + auto const n_components = object_finder.size(); std::vector topo_obj_idx(n_math_topologies); std::vector topo_component_idx(n_math_topologies); @@ -506,8 +549,7 @@ class Topology { if (!include(component_i)) { continue; } - Idx const obj_idx = component_obj_idx[component_i]; - Idx2D const math_idx = objects[obj_idx]; + Idx2D const math_idx = object_finder.find_math_object(component_i); Idx const topo_idx = math_idx.group; if (topo_idx >= 0) { // Consider non-isolated objects only topo_obj_idx[topo_idx].push_back(math_idx.pos); @@ -517,22 +559,22 @@ class Topology { // Couple components per topology for (Idx topo_idx = 0; topo_idx != n_math_topologies; ++topo_idx) { - auto const obj_idx = topo_obj_idx[topo_idx]; - auto const n_obj = (math_topology_[topo_idx].*n_obj_fn)(); + IdxVector const& obj_idx = topo_obj_idx[topo_idx]; + Idx const n_obj = (math_topology_[topo_idx].*n_obj_fn)(); // Reorder to compressed format for each math topology - auto map = build_sparse_mapping(obj_idx, n_obj); + SparseMapping map = build_sparse_mapping(obj_idx, n_obj); // Assign indptr math_topology_[topo_idx].*indptr = std::move(map.indptr); // Reorder components within the math model - auto const& reorder = map.reorder; + IdxVector const& reorder = map.reorder; // Store component coupling for the current topology for (Idx new_math_comp_i = 0; new_math_comp_i != (Idx)reorder.size(); ++new_math_comp_i) { - auto const old_math_comp_i = reorder[new_math_comp_i]; - auto const topo_comp_i = topo_component_idx[topo_idx][old_math_comp_i]; + Idx const old_math_comp_i = reorder[new_math_comp_i]; + Idx const topo_comp_i = topo_component_idx[topo_idx][old_math_comp_i]; coupling[topo_comp_i] = Idx2D{topo_idx, new_math_comp_i}; } } @@ -541,11 +583,11 @@ class Topology { void couple_all_appliance() { // shunt couple_object_components<&MathModelTopology::shunt_bus_indptr, &MathModelTopology::n_bus>( - comp_topo_.shunt_node_idx, comp_coup_.node, comp_coup_.shunt); + {comp_topo_.shunt_node_idx, comp_coup_.node}, comp_coup_.shunt); // load gen couple_object_components<&MathModelTopology::load_gen_bus_indptr, &MathModelTopology::n_bus>( - comp_topo_.load_gen_node_idx, comp_coup_.node, comp_coup_.load_gen); + {comp_topo_.load_gen_node_idx, comp_coup_.node}, comp_coup_.load_gen); // set load gen type // resize vector @@ -563,7 +605,7 @@ class Topology { // source couple_object_components<&MathModelTopology::source_bus_indptr, &MathModelTopology::n_bus>( - comp_topo_.source_node_idx, comp_coup_.node, comp_coup_.source, [&](Idx i) { + {comp_topo_.source_node_idx, comp_coup_.node}, comp_coup_.source, [&](Idx i) { return comp_conn_.source_connected[i]; }); } @@ -571,36 +613,45 @@ class Topology { void couple_sensors() { // voltage sensors couple_object_components<&MathModelTopology::voltage_sensor_indptr, &MathModelTopology::n_bus>( - comp_topo_.voltage_sensor_node_idx, comp_coup_.node, comp_coup_.voltage_sensor); + {comp_topo_.voltage_sensor_node_idx, comp_coup_.node}, comp_coup_.voltage_sensor); // source power sensors couple_object_components<&MathModelTopology::source_power_sensor_indptr, &MathModelTopology::n_source>( - comp_topo_.power_sensor_object_idx, comp_coup_.source, comp_coup_.power_sensor, [&](Idx i) { + {comp_topo_.power_sensor_object_idx, comp_coup_.source}, comp_coup_.power_sensor, [&](Idx i) { return comp_topo_.power_sensor_terminal_type[i] == MeasuredTerminalType::source; }); // shunt power sensors couple_object_components<&MathModelTopology::shunt_power_sensor_indptr, &MathModelTopology::n_shunt>( - comp_topo_.power_sensor_object_idx, comp_coup_.shunt, comp_coup_.power_sensor, [&](Idx i) { + {comp_topo_.power_sensor_object_idx, comp_coup_.shunt}, comp_coup_.power_sensor, [&](Idx i) { return comp_topo_.power_sensor_terminal_type[i] == MeasuredTerminalType::shunt; }); // load + generator power sensors couple_object_components<&MathModelTopology::load_gen_power_sensor_indptr, &MathModelTopology::n_load_gen>( - comp_topo_.power_sensor_object_idx, comp_coup_.load_gen, comp_coup_.power_sensor, [&](Idx i) { + {comp_topo_.power_sensor_object_idx, comp_coup_.load_gen}, comp_coup_.power_sensor, [&](Idx i) { return comp_topo_.power_sensor_terminal_type[i] == MeasuredTerminalType::load || comp_topo_.power_sensor_terminal_type[i] == MeasuredTerminalType::generator; }); // branch 'from' power sensors + // include all branch3 sensors + auto const predicate_from_sensor = [&](Idx i) { + return comp_topo_.power_sensor_terminal_type[i] == MeasuredTerminalType::branch_from || + // all branch3 sensors are at from side in the mathemtical model + comp_topo_.power_sensor_terminal_type[i] == MeasuredTerminalType::branch3_1 || + comp_topo_.power_sensor_terminal_type[i] == MeasuredTerminalType::branch3_2 || + comp_topo_.power_sensor_terminal_type[i] == MeasuredTerminalType::branch3_3; + }; + SensorBranchObjectFinder const object_finder_from_sensor{comp_topo_.power_sensor_object_idx, + comp_topo_.power_sensor_terminal_type, + comp_coup_.branch, comp_coup_.branch3}; couple_object_components<&MathModelTopology::branch_from_power_sensor_indptr, &MathModelTopology::n_branch>( - comp_topo_.power_sensor_object_idx, comp_coup_.branch, comp_coup_.power_sensor, [&](Idx i) { - return comp_topo_.power_sensor_terminal_type[i] == MeasuredTerminalType::branch_from; - }); + object_finder_from_sensor, comp_coup_.power_sensor, predicate_from_sensor); // branch 'to' power sensors couple_object_components<&MathModelTopology::branch_to_power_sensor_indptr, &MathModelTopology::n_branch>( - comp_topo_.power_sensor_object_idx, comp_coup_.branch, comp_coup_.power_sensor, [&](Idx i) { + {comp_topo_.power_sensor_object_idx, comp_coup_.branch}, comp_coup_.power_sensor, [&](Idx i) { return comp_topo_.power_sensor_terminal_type[i] == MeasuredTerminalType::branch_to; }); } diff --git a/src/power_grid_model/__init__.py b/src/power_grid_model/__init__.py index cc700a3a6d..976d00a4d8 100644 --- a/src/power_grid_model/__init__.py +++ b/src/power_grid_model/__init__.py @@ -8,6 +8,7 @@ from power_grid_model._power_grid_core import PowerGridModel, initialize_array, power_grid_meta_data from power_grid_model.enum import ( + Branch3Side, BranchSide, CalculationMethod, CalculationType, diff --git a/src/power_grid_model/enum.py b/src/power_grid_model/enum.py index eddf70e3c4..0256514ce2 100644 --- a/src/power_grid_model/enum.py +++ b/src/power_grid_model/enum.py @@ -39,6 +39,14 @@ class BranchSide(IntEnum): to_side = 1 +class Branch3Side(IntEnum): + """Branch3 Sides""" + + side_1 = 0 + side_2 = 1 + side_3 = 2 + + class CalculationType(Enum): """Calculation Types""" @@ -65,3 +73,6 @@ class MeasuredTerminalType(IntEnum): shunt = 3 load = 4 generator = 5 + branch3_1 = 6 + branch3_2 = 7 + branch3_3 = 8 diff --git a/src/power_grid_model/validation/validation.py b/src/power_grid_model/validation/validation.py index 12278ed286..84f840d314 100644 --- a/src/power_grid_model/validation/validation.py +++ b/src/power_grid_model/validation/validation.py @@ -15,7 +15,14 @@ from power_grid_model import power_grid_meta_data from power_grid_model.data_types import BatchDataset, Dataset, SingleDataset -from power_grid_model.enum import BranchSide, CalculationType, LoadGenType, MeasuredTerminalType, WindingType +from power_grid_model.enum import ( + Branch3Side, + BranchSide, + CalculationType, + LoadGenType, + MeasuredTerminalType, + WindingType, +) from power_grid_model.utils import convert_batch_dataset_to_batch_list from power_grid_model.validation.errors import ( IdNotInDatasetError, @@ -247,6 +254,34 @@ def validate_required_values( "tap_max", "tap_size", ] + # Branch3 + required["branch3"] = required["base"] + ["node_1", "node_2", "node_3", "status_1", "status_2", "status_3"] + required["three_winding_transformer"] = required["branch3"] + [ + "u1", + "u2", + "u3", + "sn_1", + "sn_2", + "sn_3", + "uk_12", + "uk_13", + "uk_23", + "pk_12", + "pk_13", + "pk_23", + "i0", + "p0", + "winding_1", + "winding_2", + "winding_3", + "clock_12", + "clock_13", + "tap_side", + "tap_pos", + "tap_min", + "tap_max", + "tap_size", + ] # Appliances required["appliance"] = required["base"] + ["node", "status"] required["source"] = required["appliance"].copy() @@ -299,6 +334,8 @@ def validate_values(data: SingleDataset) -> List[ValidationError]: # pylint: di errors += validate_branch(data, "link") if "transformer" in data: errors += validate_transformer(data) + if "three_winding_transformer" in data: + errors += validate_three_winding_transformer(data) if "source" in data: errors += validate_source(data) if "sym_load" in data: @@ -384,6 +421,88 @@ def validate_transformer(data: SingleDataset) -> List[ValidationError]: return errors +def validate_branch3(data: SingleDataset, component: str) -> List[ValidationError]: + errors = validate_base(data, component) + errors += all_valid_ids(data, component, "node_1", "node") + errors += all_valid_ids(data, component, "node_2", "node") + errors += all_valid_ids(data, component, "node_3", "node") + errors += all_not_two_values_equal(data, component, "node_1", "node_2") + errors += all_not_two_values_equal(data, component, "node_1", "node_3") + errors += all_not_two_values_equal(data, component, "node_2", "node_3") + errors += all_boolean(data, component, "status_1") + errors += all_boolean(data, component, "status_2") + errors += all_boolean(data, component, "status_3") + return errors + + +# pylint: disable=R0915 +def validate_three_winding_transformer(data: SingleDataset) -> List[ValidationError]: + errors = validate_branch3(data, "three_winding_transformer") + errors += all_greater_than_zero(data, "three_winding_transformer", "u1") + errors += all_greater_than_zero(data, "three_winding_transformer", "u2") + errors += all_greater_than_zero(data, "three_winding_transformer", "u3") + errors += all_greater_than_zero(data, "three_winding_transformer", "sn_1") + errors += all_greater_than_zero(data, "three_winding_transformer", "sn_2") + errors += all_greater_than_zero(data, "three_winding_transformer", "sn_3") + errors += all_greater_or_equal(data, "three_winding_transformer", "uk_12", "pk_12/sn_1") + errors += all_greater_or_equal(data, "three_winding_transformer", "uk_12", "pk_12/sn_2") + errors += all_greater_or_equal(data, "three_winding_transformer", "uk_13", "pk_13/sn_1") + errors += all_greater_or_equal(data, "three_winding_transformer", "uk_13", "pk_13/sn_3") + errors += all_greater_or_equal(data, "three_winding_transformer", "uk_23", "pk_23/sn_2") + errors += all_greater_or_equal(data, "three_winding_transformer", "uk_23", "pk_23/sn_3") + errors += all_between(data, "three_winding_transformer", "uk_12", 0, 1) + errors += all_between(data, "three_winding_transformer", "uk_13", 0, 1) + errors += all_between(data, "three_winding_transformer", "uk_23", 0, 1) + errors += all_greater_than_or_equal_to_zero(data, "three_winding_transformer", "pk_12") + errors += all_greater_than_or_equal_to_zero(data, "three_winding_transformer", "pk_13") + errors += all_greater_than_or_equal_to_zero(data, "three_winding_transformer", "pk_23") + errors += all_greater_or_equal(data, "three_winding_transformer", "i0", "p0/sn_1") + errors += all_less_than(data, "three_winding_transformer", "i0", 1) + errors += all_greater_than_or_equal_to_zero(data, "three_winding_transformer", "p0") + errors += all_valid_enum_values(data, "three_winding_transformer", "winding_1", WindingType) + errors += all_valid_enum_values(data, "three_winding_transformer", "winding_2", WindingType) + errors += all_valid_enum_values(data, "three_winding_transformer", "winding_3", WindingType) + errors += all_between_or_at(data, "three_winding_transformer", "clock_12", 0, 12) + errors += all_between_or_at(data, "three_winding_transformer", "clock_13", 0, 12) + errors += all_clocks_valid(data, "three_winding_transformer", "clock_12", "winding_1", "winding_2") + errors += all_clocks_valid(data, "three_winding_transformer", "clock_13", "winding_1", "winding_3") + errors += all_valid_enum_values(data, "three_winding_transformer", "tap_side", Branch3Side) + errors += all_between_or_at(data, "three_winding_transformer", "tap_pos", "tap_min", "tap_max") + errors += all_between_or_at(data, "three_winding_transformer", "tap_nom", "tap_min", "tap_max") + errors += all_greater_than_or_equal_to_zero(data, "three_winding_transformer", "tap_size") + errors += all_greater_or_equal(data, "three_winding_transformer", "uk_12_min", "pk_12_min/sn_1") + errors += all_greater_or_equal(data, "three_winding_transformer", "uk_12_min", "pk_12_min/sn_2") + errors += all_greater_or_equal(data, "three_winding_transformer", "uk_13_min", "pk_13_min/sn_1") + errors += all_greater_or_equal(data, "three_winding_transformer", "uk_13_min", "pk_13_min/sn_3") + errors += all_greater_or_equal(data, "three_winding_transformer", "uk_23_min", "pk_23_min/sn_2") + errors += all_greater_or_equal(data, "three_winding_transformer", "uk_23_min", "pk_23_min/sn_3") + errors += all_between(data, "three_winding_transformer", "uk_12_min", 0, 1) + errors += all_between(data, "three_winding_transformer", "uk_13_min", 0, 1) + errors += all_between(data, "three_winding_transformer", "uk_23_min", 0, 1) + errors += all_greater_or_equal(data, "three_winding_transformer", "uk_12_max", "pk_12_max/sn_1") + errors += all_greater_or_equal(data, "three_winding_transformer", "uk_12_max", "pk_12_max/sn_2") + errors += all_greater_or_equal(data, "three_winding_transformer", "uk_13_max", "pk_13_max/sn_1") + errors += all_greater_or_equal(data, "three_winding_transformer", "uk_13_max", "pk_13_max/sn_3") + errors += all_greater_or_equal(data, "three_winding_transformer", "uk_23_max", "pk_23_max/sn_2") + errors += all_greater_or_equal(data, "three_winding_transformer", "uk_23_max", "pk_23_max/sn_3") + errors += all_between(data, "three_winding_transformer", "uk_12_max", 0, 1) + errors += all_between(data, "three_winding_transformer", "uk_13_max", 0, 1) + errors += all_between(data, "three_winding_transformer", "uk_23_max", 0, 1) + errors += all_greater_than_or_equal_to_zero(data, "three_winding_transformer", "uk_12_min") + errors += all_greater_than_or_equal_to_zero(data, "three_winding_transformer", "uk_13_min") + errors += all_greater_than_or_equal_to_zero(data, "three_winding_transformer", "uk_23_min") + errors += all_greater_than_or_equal_to_zero(data, "three_winding_transformer", "uk_12_max") + errors += all_greater_than_or_equal_to_zero(data, "three_winding_transformer", "uk_13_max") + errors += all_greater_than_or_equal_to_zero(data, "three_winding_transformer", "uk_23_max") + errors += all_greater_than_or_equal_to_zero(data, "three_winding_transformer", "pk_12_min") + errors += all_greater_than_or_equal_to_zero(data, "three_winding_transformer", "pk_13_min") + errors += all_greater_than_or_equal_to_zero(data, "three_winding_transformer", "pk_23_min") + errors += all_greater_than_or_equal_to_zero(data, "three_winding_transformer", "pk_12_max") + errors += all_greater_than_or_equal_to_zero(data, "three_winding_transformer", "pk_13_max") + errors += all_greater_than_or_equal_to_zero(data, "three_winding_transformer", "pk_23_max") + return errors + + def validate_appliance(data: SingleDataset, component: str) -> List[ValidationError]: errors = validate_base(data, component) errors += all_boolean(data, component, "status") diff --git a/tests/cpp_unit_tests/CMakeLists.txt b/tests/cpp_unit_tests/CMakeLists.txt index fcfe1a1303..bb1cf887b8 100644 --- a/tests/cpp_unit_tests/CMakeLists.txt +++ b/tests/cpp_unit_tests/CMakeLists.txt @@ -30,6 +30,7 @@ set(PROJECT_SOURCES test_voltage_sensor.cpp test_power_sensor.cpp test_validation.cpp + test_three_winding_transformer.cpp ) add_executable(power_grid_model_unit_tests ${PROJECT_SOURCES}) diff --git a/tests/cpp_unit_tests/test_three_winding_transformer.cpp b/tests/cpp_unit_tests/test_three_winding_transformer.cpp new file mode 100644 index 0000000000..e623146625 --- /dev/null +++ b/tests/cpp_unit_tests/test_three_winding_transformer.cpp @@ -0,0 +1,447 @@ +// SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project +// +// SPDX-License-Identifier: MPL-2.0 + +#include "doctest/doctest.h" +#include "power_grid_model/component/three_winding_transformer.hpp" +#include "power_grid_model/component/transformer.hpp" + +namespace power_grid_model { + +using namespace std::complex_literals; + +TEST_CASE("Test three winding transformer") { + ThreeWindingTransformerInputBasics input_basics{ + {{1}, 2, 3, 4, true, true, true}, // Create branch3 {{id}, node_1, node_2, node_3, status_1, status_2, + // status_3} + 138e3, // u1 + 69e3, // u2 + 13.8e3, // u3 + 60e6, // s1 + 50e6, // s2 + 10e6, // s3 + 0.09, // uk12 + 0.06, // uk13 + 0.03, // uk23 + 200e3, // pk_12 + 150e3, // pk_13 + 100e3, // pk_23 + 0.1, // i0 + 50e3, // p0 + WindingType::wye_n, // winding_12 + WindingType::delta, // winding_13 + WindingType::delta, // winding_23 + 1, // clock_12 + 1, // clock_13 + Branch3Side::side_1, // tap side + 2, // tap_pos + -8, // tap_min + 10, // tap_max + 0, // tap_nom + 1380 // tap size + }; + + ThreeWindingTransformerInput input{ + input_basics, + nan, // uk_12_min + nan, // uk_12_max + nan, // uk_13_min + nan, // uk_13_max + nan, // uk_23_min + nan, // uk_23_max + nan, // pk_12_min + nan, // pk_12_max + nan, // pk_13_min + nan, // pk_13_max + nan, // pk_23_min + nan, // pk_23_max + 1.0, // r1_grounding + 4.0, // x1_grounding + nan, // r2_grounding + nan, // x2_grounding + nan, // r3_grounding + nan // x3_grounding + }; + + // Check what transformers should be tested + std::vector vec; + // 0 YN d1 d1 + vec.emplace_back(input, 138e3, 69e3, 13.8e3); + // 1 d YN1 YN1 + input.winding_1 = WindingType::delta; + input.winding_2 = WindingType::wye_n; + input.winding_3 = WindingType::wye_n; + vec.emplace_back(input, 138e3, 69e3, 13.8e3); + // 2 YN yn12 d1 + input.winding_1 = WindingType::wye_n; + input.winding_3 = WindingType::delta; + input.clock_12 = 12; + input.r_grounding_2 = 2.0; + input.x_grounding_2 = 6.0; + vec.emplace_back(input, 138e3, 69e3, 13.8e3); + // 3 Yn y12 d1 + input.winding_2 = WindingType::wye; + vec.emplace_back(input, 138e3, 69e3, 13.8e3); + // 4 tap max limit and side 2 + input.tap_side = Branch3Side::side_2; + input.tap_pos = 12; + vec.emplace_back(input, 138e3, 69e3, 13.8e3); + // 5 tap min limit and side 3 + input.tap_side = Branch3Side::side_3; + input.tap_pos = -14; + vec.emplace_back(input, 138e3, 69e3, 13.8e3); + // 6 reverse tap + input.tap_pos = 2; + input.tap_max = -10; + input.tap_min = 8; + vec.emplace_back(input, 138e3, 69e3, 13.8e3); + // 7 uk,pk min and max + input.uk_12_min = 0.08; + input.uk_12_max = 0.09; + input.uk_13_min = 0.07; + input.uk_13_max = 0.05; + input.uk_23_min = 0.02; + input.uk_23_max = 0.04; + input.pk_12_min = 180e3; + input.pk_12_max = 220e3; + input.pk_13_min = 130e3; + input.pk_13_max = 170e3; + input.pk_23_min = 80e3; + input.pk_23_max = 120e3; + vec.emplace_back(input, 138e3, 69e3, 13.8e3); + + for (ThreeWindingTransformer& transformer3 : vec) { + CHECK(transformer3.math_model_type() == ComponentType::branch3); + } + + double const uk_t1 = 0.5 * (0.09 / 50 + 0.06 / 10 - 0.03 / 10) * 60; + double const uk_t2 = 0.5 * (0.09 / 50 - 0.06 / 10 + 0.03 / 10) * 50; + double const uk_t3 = 0.5 * (-0.09 / 50 + 0.06 / 10 + 0.03 / 10) * 10; + + double const pk_t1 = 0.5 * (200e3 / 50 / 50 + 150e3 / 10 / 10 - 100e3 / 10 / 10) * 60 * 60; + double const pk_t2 = 0.5 * (200e3 / 50 / 50 - 150e3 / 10 / 10 + 100e3 / 10 / 10) * 50 * 50; + double const pk_t3 = 0.5 * (-200e3 / 50 / 50 + 150e3 / 10 / 10 + 100e3 / 10 / 10) * 10 * 10; + + double const u_t1 = 138e3 + 1 * 2 * 1380; + + // ******************** + double const base_i_1 = base_power_3p / 138e3 / sqrt3; + double const base_i_2 = base_power_3p / 69e3 / sqrt3; + double const base_i_3 = base_power_3p / 13.8e3 / sqrt3; + + // add for reverse tap and different side taps + + // Add test for grounding too + + TransformerInput T1_input{ + {{2}, 0, 1, true, true}, // {{id}, from_node, to_node, from_status, to_status} + u_t1, // u1 + u_t1, // u2 + 60e6, // sn + uk_t1, // uk + pk_t1, // pk + 0.1, // i0 + 50e3, // p0 + WindingType::wye_n, // winding_from + WindingType::wye_n, // winding_to + 0, // clock + BranchSide::from, // tap_side + 0, // tap_pos + 0, // tap_min + 0, // tap_max + 0, // tap_nom + 0.0, // tap_size + nan, // uk_min + nan, // uk_max + nan, // pk_min + nan, // pk_max + 1.0, // r_grounding_from + 4.0, // x_grounding_from + 0, // r_grounding_to + 0 // x_grounding_to + }; + TransformerInput T2_input{ + {{2}, 0, 1, true, true}, // {{id}, from_node, to_node, from_status, to_status} + 69e3, // u1 + u_t1, // u2 + 50e6, // sn + uk_t2, // uk + pk_t2, // pk + 0.0, // i0 + 0.0, // p0 + WindingType::delta, // winding_from + WindingType::wye_n, // winding_to + 11, // clock, reversed + BranchSide::from, // tap_side + 0, // tap_pos + 0, // tap_min + 0, // tap_max + 0, // tap_nom + 0.0, // tap_size + nan, // uk_min + nan, // uk_max + nan, // pk_min + nan, // pk_max + 0, // r_grounding_from + 0, // x_grounding_from + 0, // r_grounding_to + 0 // x_grounding_to + }; + TransformerInput T3_input{ + {{2}, 0, 1, true, true}, // {{id}, from_node, to_node, from_status, to_status} + 13.8e3, // u1 + u_t1, // u2 + 10e6, // sn + uk_t3, // uk + pk_t3, // pk + 0.0, // i0 + 0.0, // p0 + WindingType::delta, // winding_from + WindingType::wye_n, // winding_to + 11, // clock, reversed + BranchSide::from, // tap_side + 0, // tap_pos + 0, // tap_min + 0, // tap_max + 0, // tap_nom + 0.0, // tap_size + nan, // uk_min + nan, // uk_max + nan, // pk_min + nan, // pk_max + 0, // r_grounding_from + 0, // x_grounding_from + 0, // r_grounding_to + 0 // x_grounding_to + }; + + auto make_trafos = [](TransformerInput T1, TransformerInput T2, TransformerInput T3) { + Transformer t1{T1, 138e3, 138e3}, t2{T2, 69e3, 138e3}, t3{T3, 13.8e3, 138e3}; + return std::array{t1, t2, t3}; + }; + + std::vector> trafos_vec; + // 0 YN d1 d1 + trafos_vec.emplace_back(make_trafos(T1_input, T2_input, T3_input)); + // 1 D YN1 YN1 + T2_input.winding_to = WindingType::delta; + T3_input.winding_to = WindingType::delta; + T2_input.winding_from = WindingType::wye_n; + T3_input.winding_from = WindingType::wye_n; + trafos_vec.emplace_back(make_trafos(T1_input, T2_input, T3_input)); + // 2 YN yn12 d1 + T2_input.winding_from = WindingType::wye_n; + T3_input.winding_from = WindingType::delta; + T2_input.winding_to = WindingType::wye_n; + T3_input.winding_to = WindingType::wye_n; + T2_input.clock = 12; + T2_input.r_grounding_from = 2.0; + T2_input.x_grounding_from = 6.0; + trafos_vec.emplace_back(make_trafos(T1_input, T2_input, T3_input)); + // 3 Yn y12 d1 + T2_input.winding_from = WindingType::wye; + trafos_vec.emplace_back(make_trafos(T1_input, T2_input, T3_input)); + // 4 tap max limit and side 2 + T1_input.u1 = 138e3; + T1_input.u2 = 138e3; + T2_input.u1 = 69e3 + 1 * 10 * 1380; + T2_input.u2 = 138e3; + T3_input.u2 = 138e3; + trafos_vec.emplace_back(make_trafos(T1_input, T2_input, T3_input)); + // 5 tap min limit and side 3 + T2_input.u1 = 69e3; + T3_input.u1 = 13.8e3 + 1 * (-8) * 1380; + trafos_vec.emplace_back(make_trafos(T1_input, T2_input, T3_input)); + // 6 reverse tap + T3_input.u1 = 13.8e3 + (-1) * 2 * 1380; + trafos_vec.emplace_back(make_trafos(T1_input, T2_input, T3_input)); + // 7 uk,pk min and max + // calculated manually + T1_input.uk = 0.1575; + T2_input.uk = -0.04375; + T3_input.uk = 0.03625; + T1_input.pk = 1040.4e3; + T2_input.pk = -527.5e3; + T3_input.pk = 116.1e3; + trafos_vec.emplace_back(make_trafos(T1_input, T2_input, T3_input)); + + // sym admittances of converted 3 2wdg transformers of 3wdg transformer vector + for (size_t trafo = 0; trafo < trafos_vec.size(); ++trafo) { + std::array, 3> calc_params, test_params = vec[trafo].calc_param(); + for (size_t i = 0; i < 3; ++i) { + calc_params[i] = trafos_vec[trafo][i].calc_param(); + } + for (size_t i = 0; i < 3; ++i) { + for (size_t value_no = 0; value_no < 3; ++value_no) { + CHECK(cabs(calc_params[i].value[value_no] - test_params[i].value[value_no]) < numerical_tolerance); + } + } + } + // asym admittance + for (size_t trafo = 0; trafo < trafos_vec.size(); ++trafo) { + std::array, 3> calc_params, test_params = vec[trafo].calc_param(); + for (size_t i = 0; i < 3; ++i) { + calc_params[i] = trafos_vec[trafo][i].calc_param(); + } + for (size_t i = 0; i < 3; i++) { + for (size_t value_no = 0; value_no < calc_params[i].value.size(); ++value_no) { + CHECK((cabs(calc_params[i].value[value_no] - test_params[i].value[value_no]) < numerical_tolerance) + .all()); + } + } + } + + // Check phase shift + std::array test_shift = vec[0].phase_shift(); + std::array shift = {0.0, -1 * deg_30, -1 * deg_30}; + for (size_t i = 0; i < 3; i++) { + CHECK(test_shift[i] == shift[i]); + } + + SUBCASE("Check output of branch 3") { + // TODO asym output check + BranchMathOutput b1_output, b2_output, b3_output; + // Branch initialization: s_f, s_t, i_f, i_t + b1_output = {(1.0 - 2.0i), (2.0 - 3.0i), (1.5 - 2.5i), (2.5 - 3.5i)}; + b2_output = {(2.0 - 3.0i), (-3.0 + 2.0i), (1.5 - 2.5i), (-4.0 + 1.5i)}; + b3_output = {(3.0 + 1.0i), (1.0 + 1.0i), (1.5 - 2.5i), (1.5 + 2.0i)}; + + Branch3Output sym_output = vec[0].get_output(b1_output, b2_output, b3_output); + + double const out_p_1 = base_power * 1; + double const out_q_1 = base_power * (-2); + double const out_i_1 = base_i_1 * cabs(b1_output.i_f); + double const out_s_1 = base_power * cabs(b1_output.s_f); + + double const out_p_2 = base_power * 2; + double const out_q_2 = base_power * (-3); + double const out_i_2 = base_i_2 * cabs(b2_output.i_f); + double const out_s_2 = base_power * cabs(b2_output.s_f); + + double const out_p_3 = base_power * 3; + double const out_q_3 = base_power * 1; + double const out_i_3 = base_i_3 * cabs(b3_output.i_f); + double const out_s_3 = base_power * cabs(b3_output.s_f); + // max loading is at branch 3 + double const out_loading = 0.316227766; + + CHECK(sym_output.id == 1); + CHECK(sym_output.energized); + CHECK(sym_output.p_1 == doctest::Approx(out_p_1)); + CHECK(sym_output.q_1 == doctest::Approx(out_q_1)); + CHECK(sym_output.i_1 == doctest::Approx(out_i_1)); + CHECK(sym_output.s_1 == doctest::Approx(out_s_1)); + CHECK(sym_output.p_2 == doctest::Approx(out_p_2)); + CHECK(sym_output.q_2 == doctest::Approx(out_q_2)); + CHECK(sym_output.i_2 == doctest::Approx(out_i_2)); + CHECK(sym_output.s_2 == doctest::Approx(out_s_2)); + CHECK(sym_output.p_3 == doctest::Approx(out_p_3)); + CHECK(sym_output.q_3 == doctest::Approx(out_q_3)); + CHECK(sym_output.i_3 == doctest::Approx(out_i_3)); + CHECK(sym_output.s_3 == doctest::Approx(out_s_3)); + CHECK(sym_output.loading == doctest::Approx(out_loading)); + + BranchMathOutput asym_b1_output, asym_b2_output, asym_b3_output; + asym_b1_output = {{(1.0 - 2.0i), (1.0 - 2.0i), (1.0 - 2.0i)}, + {(2.0 - 3.0i), (2.0 - 3.0i), (2.0 - 3.0i)}, + {(1.5 - 2.5i), (1.5 - 2.5i), (1.5 - 2.5i)}, + {(2.5 - 3.5i), (2.5 - 3.5i), (2.5 - 3.5i)}}; + asym_b2_output = {{(2.0 - 3.0i), (2.0 - 3.0i), (2.0 - 3.0i)}, + {(-3.0 + 2.0i), (-3.0 + 2.0i), (-3.0 + 2.0i)}, + {(1.5 - 2.5i), (1.5 - 2.5i), (1.5 - 2.5i)}, + {(-4.0 + 1.5i), (-4.0 + 1.5i), (-4.0 + 1.5i)}}; + asym_b3_output = {{(3.0 + 1.0i), (3.0 + 1.0i), (3.0 + 1.0i)}, + {(1.0 + 1.0i), (1.0 + 1.0i), (1.0 + 1.0i)}, + {(1.5 - 2.5i), (1.5 - 2.5i), (1.5 - 2.5i)}, + {(1.5 + 2.0i), (1.5 + 2.0i), (1.5 + 2.0i)}}; + + Branch3Output asym_output = vec[0].get_output(asym_b1_output, asym_b2_output, asym_b3_output); + + CHECK(asym_output.id == 1); + CHECK(asym_output.energized); + CHECK(asym_output.p_1(0) == doctest::Approx(out_p_1 / 3)); + CHECK(asym_output.q_1(1) == doctest::Approx(out_q_1 / 3)); + CHECK(asym_output.i_1(2) == doctest::Approx(out_i_1)); + CHECK(asym_output.s_1(0) == doctest::Approx(out_s_1 / 3)); + CHECK(asym_output.p_2(1) == doctest::Approx(out_p_2 / 3)); + CHECK(asym_output.q_2(2) == doctest::Approx(out_q_2 / 3)); + CHECK(asym_output.i_2(0) == doctest::Approx(out_i_2)); + CHECK(asym_output.s_2(1) == doctest::Approx(out_s_2 / 3)); + CHECK(asym_output.p_3(2) == doctest::Approx(out_p_3 / 3)); + CHECK(asym_output.q_3(0) == doctest::Approx(out_q_3 / 3)); + CHECK(asym_output.i_3(1) == doctest::Approx(out_i_3)); + CHECK(asym_output.s_3(2) == doctest::Approx(out_s_3 / 3)); + CHECK(asym_output.loading == doctest::Approx(out_loading)); + } + + SUBCASE("No source results") { + Branch3Output output = vec[0].get_null_output(); + CHECK(output.id == 1); + CHECK(!output.energized); + CHECK(output.p_1(0) == 0); + CHECK(output.q_1(1) == 0); + CHECK(output.i_1(2) == 0); + CHECK(output.s_1(0) == 0); + CHECK(output.p_2(1) == 0); + CHECK(output.q_2(2) == 0); + CHECK(output.i_2(0) == 0); + CHECK(output.s_2(1) == 0); + CHECK(output.p_3(2) == 0); + CHECK(output.q_3(0) == 0); + CHECK(output.i_3(1) == 0); + CHECK(output.s_3(2) == 0); + CHECK(output.loading == 0); + } + + SUBCASE("invalid input") { + input.node_2 = 2; + CHECK_THROWS_AS(ThreeWindingTransformer(input, 138e3, 69e3, 13.8e3), InvalidBranch3); + input.node_2 = 3; + } + + SUBCASE("Test i base") { + CHECK(vec[0].base_i_1() == doctest::Approx(base_i_1)); + CHECK(vec[0].base_i_2() == doctest::Approx(base_i_2)); + CHECK(vec[0].base_i_3() == doctest::Approx(base_i_3)); + } + + SUBCASE("update - check changed") { + SUBCASE("update tap") { + auto changed = vec[0].update(ThreeWindingTransformerUpdate{{{1}, na_IntS, na_IntS, na_IntS}, -2}); + CHECK(!changed.topo); + CHECK(changed.param); + } + SUBCASE("update status_1") { + auto changed = vec[0].update(ThreeWindingTransformerUpdate{{{1}, false, true, true}, na_IntS}); + CHECK(changed.topo); + CHECK(changed.param); + } + SUBCASE("update status_2") { + auto changed = vec[0].update(ThreeWindingTransformerUpdate{{{1}, true, false, true}, na_IntS}); + CHECK(changed.topo); + CHECK(changed.param); + } + SUBCASE("update status_3") { + auto changed = vec[0].update(ThreeWindingTransformerUpdate{{{1}, true, true, false}, na_IntS}); + CHECK(changed.topo); + CHECK(changed.param); + } + SUBCASE("update status") { + auto changed = vec[0].update(ThreeWindingTransformerUpdate{{{1}, false, false, false}, na_IntS}); + CHECK(changed.topo); + CHECK(changed.param); + } + SUBCASE("update status & tap") { + auto changed = vec[0].update(ThreeWindingTransformerUpdate{{{1}, false, false, false}, -2}); + CHECK(changed.topo); + CHECK(changed.param); + } + SUBCASE("update none") { + auto changed = vec[0].update(ThreeWindingTransformerUpdate{{{1}, na_IntS, na_IntS, na_IntS}, na_IntS}); + CHECK(!changed.topo); + CHECK(!changed.param); + } + } +} + +} // namespace power_grid_model diff --git a/tests/cpp_unit_tests/test_topology.cpp b/tests/cpp_unit_tests/test_topology.cpp index 98ad7b98df..ffb66a39ec 100644 --- a/tests/cpp_unit_tests/test_topology.cpp +++ b/tests/cpp_unit_tests/test_topology.cpp @@ -26,26 +26,26 @@ * * 7 -> [5:s1+p1+p12,lg2+p4+p8,v4] [6:h1+p5+p9] -X-4-> [7] -3-> [8,v5] * 0 ----->+p13 [1:lg3+p7,v1] / / \ / \ / - * / \ 5 ---X--- [4] <- 6 $2 $1 $1 $2 + * / +p14\ 5 ---X--- [4] <- 6 $2 $1 $1 $2 * / $0 / ^ \ / \ / * / \ v / (b2) (b1) * [0:s0,lg0] (b0)-$2- [2:v0,v2] / | | - * +p0+p11 / X $0 $0 + * +p0+p11 / +p15 X $0 $0 * \ $1 / [9:s2X+p3,h2] X | - * \ / / [10] [11:lg1+p6] + * \ +p16/ / [10] [11:lg1+p6] * 1 -->+p2+p10 [3:s3X,h0,v3] -- 2 * * * Math model #0: Math model #1: * * 0 ----->+pt0 [2:lg0+pg0,v3] 1 -> [3:s0+ps0+ps1,lg0+pl0+pl1,v0] [0:h1+ps0+ps1] - * / ^ 3 --X / / \ / + * / +pf2\ 3 --X / / \ / * / 4 / [2] <- 0 3 4 - * / \ v \ / - * [4:s0,lg1] [3] -6-> [0:v0,v1] [1] - * +pf0+pf1 / | + * / v v v v + * [4:s0,lg1] [3] <-6- [0:v0,v1] [1] + * +pf0+pf1 ^ +pf4 ^ * \ 5 2 - * \ v X + * \ +pf3/ X * 1 ->+pt1+pt2 [1:h0,v2] -- 2 --X * * Extra fill-in: @@ -125,7 +125,7 @@ TEST_CASE("Test topology") { LoadGenType::const_y}; comp_topo.shunt_node_idx = {3, 6, 9}; comp_topo.voltage_sensor_node_idx = {2, 1, 2, 3, 5, 8}; - comp_topo.power_sensor_object_idx = {1, 1, 1, 2, 2, 1, 1, 3, 2, 1, 1, 1, 1, 0}; + comp_topo.power_sensor_object_idx = {1, 1, 1, 2, 2, 1, 1, 3, 2, 1, 1, 1, 1, 0, 0, 0, 0}; comp_topo.power_sensor_terminal_type = { MeasuredTerminalType::branch_from, // 0 (branch 1) MeasuredTerminalType::source, // 1 (source 1) @@ -141,6 +141,9 @@ TEST_CASE("Test topology") { MeasuredTerminalType::branch_from, // 11 (branch 1) MeasuredTerminalType::source, // 12 (source 1) MeasuredTerminalType::branch_to, // 13 (branch 0) + MeasuredTerminalType::branch3_1, // 14 (branch3 0) + MeasuredTerminalType::branch3_3, // 15 (branch3 0) + MeasuredTerminalType::branch3_2, // 16 (branch3 0) }; // component connection @@ -229,7 +232,10 @@ TEST_CASE("Test topology") { {0, 2}, // 10 branch_to {0, 1}, // 11 branch_from {1, 1}, // 12 source - {0, 0} // 13 branch_to + {0, 0}, // 13 branch_to + {0, 2}, // 14 branch_from + {0, 4}, // 15 branch_from + {0, 3} // 16 branch_from }; // Sub graph / math model 0 @@ -245,7 +251,7 @@ TEST_CASE("Test topology") { math0.source_power_sensor_indptr = {0, 0}; math0.shunt_power_sensor_indptr = {0, 0}; math0.load_gen_power_sensor_indptr = {0, 1, 1}; - math0.branch_from_power_sensor_indptr = {0, 0, 2, 2, 2, 2, 2, 2}; + math0.branch_from_power_sensor_indptr = {0, 0, 2, 2, 2, 3, 4, 5}; // 7 branches, 3 branch-to power sensors // sensor 0 is connected to branch 0 // sensor 1 and 2 are connected to branch 1 diff --git a/tests/cpp_unit_tests/test_transformer.cpp b/tests/cpp_unit_tests/test_transformer.cpp index acdcb04d52..0dff2f18b8 100644 --- a/tests/cpp_unit_tests/test_transformer.cpp +++ b/tests/cpp_unit_tests/test_transformer.cpp @@ -154,6 +154,39 @@ TEST_CASE("Test transformer") { } } + SUBCASE("update - check changed") { + SUBCASE("update tap") { + auto changed = vec[0].update(TransformerUpdate{{{1}, na_IntS, na_IntS}, -2}); + CHECK(!changed.topo); + CHECK(changed.param); + } + SUBCASE("update from_status") { + auto changed = vec[0].update(TransformerUpdate{{{1}, false, true}, na_IntS}); + CHECK(changed.topo); + CHECK(changed.param); + } + SUBCASE("update to_status") { + auto changed = vec[0].update(TransformerUpdate{{{1}, true, false}, na_IntS}); + CHECK(changed.topo); + CHECK(changed.param); + } + SUBCASE("update status") { + auto changed = vec[0].update(TransformerUpdate{{{1}, false, false}, na_IntS}); + CHECK(changed.topo); + CHECK(changed.param); + } + SUBCASE("update status & tap") { + auto changed = vec[0].update(TransformerUpdate{{{1}, false, false}, -2}); + CHECK(changed.topo); + CHECK(changed.param); + } + SUBCASE("update none") { + auto changed = vec[0].update(TransformerUpdate{{{1}, na_IntS, na_IntS}, na_IntS}); + CHECK(!changed.topo); + CHECK(!changed.param); + } + } + SUBCASE("asymmetric paramters") { for (size_t i = 0; i < 5; i++) { vec[i].set_tap(-2); diff --git a/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/README.md b/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/README.md new file mode 100644 index 0000000000..ab57d349bd --- /dev/null +++ b/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/README.md @@ -0,0 +1,16 @@ + +## Component Test Case: Three Winding Transformer + +Test case for validation of the three winding transformer component for symmetrical power flow calculations in pandapower. The implementation of both the libraries are similar. Three 2-winding transformers are used in star connection for creating a 3 winding transformer. +- A three transformer can have multiple states from the node end status being open or closed. However only the all working status is being tested here for the sake of simplicity. + +The circuit diagram is as follows: +``` +source_7--node_1--three_winding_transformer_3--node_2---sym_load_41 (3wdg Transformer status=1) + | + node_3---sym_load_42 +``` diff --git a/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/input.json b/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/input.json new file mode 100644 index 0000000000..9c1ce4467b --- /dev/null +++ b/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/input.json @@ -0,0 +1,63 @@ +{ + "node": [ + { + "id": 1, + "u_rated": 138000.0 + }, + { + "id": 2, + "u_rated": 69000.0 + }, + { + "id": 3, + "u_rated": 13800.0 + } + ], + "three_winding_transformer": [ + { + "id": 4, + "node_1": 1, + "node_2": 2, + "node_3": 3, + "status_1": 1, + "status_2": 1, + "status_3": 1, + "u1": 138000.0, + "u2": 69000.0, + "u3": 13800.0, + "sn_1": 60000000.0, + "sn_2": 50000000.0, + "sn_3": 10000000.0, + "uk_12": 0.09, + "uk_13": 0.03, + "uk_23": 0.06, + "pk_12": 50000.0, + "pk_13": 5000.0, + "pk_23": 10000.0, + "i0": 0.001, + "p0": 50000.0, + "winding_1": 2, + "winding_2": 1, + "winding_3": 1, + "clock_12": 11, + "clock_13": 11, + "tap_side": 0, + "tap_pos": -8, + "tap_min": -8, + "tap_max": 10, + "tap_nom": 0, + "tap_size": 1380.0 + } + ], + "source": [ + { + "id": 7, + "node": 1, + "status": 1, + "u_ref": 1.0, + "sk": 1000000000000.0, + "rx_ratio": 0.1, + "z01_ratio": 1.0 + } + ] +} \ No newline at end of file diff --git a/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/input.json.license b/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/input.json.license new file mode 100644 index 0000000000..ebe16ce854 --- /dev/null +++ b/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/input.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project + +SPDX-License-Identifier: MPL-2.0 \ No newline at end of file diff --git a/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/params.json b/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/params.json new file mode 100644 index 0000000000..a96e931dcf --- /dev/null +++ b/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/params.json @@ -0,0 +1,7 @@ +{ + "calculation_method": "newton_raphson", + "rtol": 1e-05, + "atol": 1e-05, + "independent": true, + "cache_topology": false +} \ No newline at end of file diff --git a/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/params.json.license b/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/params.json.license new file mode 100644 index 0000000000..ebe16ce854 --- /dev/null +++ b/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/params.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project + +SPDX-License-Identifier: MPL-2.0 \ No newline at end of file diff --git a/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/sym_output_batch.json b/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/sym_output_batch.json new file mode 100644 index 0000000000..096b805bd9 --- /dev/null +++ b/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/sym_output_batch.json @@ -0,0 +1,372 @@ +[ + { + "node": [ + { + "id": 1, + "u_pu": 0.9999999551315109, + "u": 137999.99380814852, + "u_angle": -5.488210325456181e-08 + }, + { + "id": 2, + "u_pu": 1.0869676929016086, + "u": 75000.77081021099, + "u_angle": 0.5236134683018021 + }, + { + "id": 3, + "u_pu": 1.0869676929016083, + "u": 15000.154162042196, + "u_angle": 0.5236134683018022 + } + ], + "three_winding_transformer": [ + { + "id": 4, + "loading": 0.001181480471889959, + "p_1": 59074.31253828018, + "q_1": 39184.838621282404, + "i_1": 0.2965774343887005, + "p_2": -6.178853223510507e-08, + "q_2": 1.0701724588638337e-07, + "i_2": 9.5126270089699e-13, + "p_3": 6.178853223510507e-08, + "q_3": -1.0701724588638333e-07, + "i_3": 4.756313504484951e-12 + } + ] + }, + { + "node": [ + { + "id": 1, + "u_pu": 0.9999999570204968, + "u": 137999.99406882856, + "u_angle": -5.257153947510249e-08 + }, + { + "id": 2, + "u_pu": 1.063840722721792, + "u": 73405.00986780366, + "u_angle": 0.523613470612366 + }, + { + "id": 3, + "u_pu": 1.0638407227217923, + "u": 14681.001973560735, + "u_angle": 0.5236134706123657 + } + ], + "three_winding_transformer": [ + { + "id": 4, + "loading": 0.0011317395599586915, + "p_1": 56587.25477739026, + "q_1": 37535.1375437802, + "i_1": 0.28409137725127986, + "p_2": 3.023694148466247e-08, + "q_2": -5.237014163303688e-08, + "i_2": 4.756313504484951e-13, + "p_3": -1.9170341410475252e-08, + "q_3": 9.36736831918866e-08, + "i_3": 3.760195984997484e-12 + } + ] + }, + { + "node": [ + { + "id": 1, + "u_pu": 0.9999999587926549, + "u": 137999.99431338636, + "u_angle": -5.04038762637355e-08 + }, + { + "id": 2, + "u_pu": 1.0416773761777725, + "u": 71875.7389562663, + "u_angle": 0.523613472780029 + }, + { + "id": 3, + "u_pu": 1.0416773761777722, + "u": 14375.147791253257, + "u_angle": 0.5236134727800292 + } + ], + "three_winding_transformer": [ + { + "id": 4, + "loading": 0.0010850749552206247, + "p_1": 54254.013127725724, + "q_1": 35987.46490023869, + "i_1": 0.27237754056636787, + "p_2": 0.0, + "q_2": 0.0, + "i_2": 0.0, + "p_3": 5.524655388854544e-08, + "q_3": -3.647559435870055e-08, + "i_3": 2.65886007958215e-12 + } + ] + }, + { + "node": [ + { + "id": 1, + "u_pu": 0.9999999604574247, + "u": 137999.9945431246, + "u_angle": -4.836756813762414e-08 + }, + { + "id": 2, + "u_pu": 1.0204186559137531, + "u": 70408.88725804897, + "u_angle": 0.5236134748163374 + }, + { + "id": 3, + "u_pu": 1.0204186559137536, + "u": 14081.777451609802, + "u_angle": 0.5236134748163372 + } + ], + "three_winding_transformer": [ + { + "id": 4, + "loading": 0.001041238111757312, + "p_1": 52062.16021058374, + "q_1": 34533.577479908876, + "i_1": 0.26137353386302686, + "p_2": 8.312185427839478e-08, + "q_2": -8.596377930552015e-08, + "i_2": 9.805391483771576e-13, + "p_3": 2.122980396381372e-08, + "q_3": 7.923536580100409e-08, + "i_3": 3.3632215324704603e-12 + } + ] + }, + { + "node": [ + { + "id": 1, + "u_pu": 0.9999999620233108, + "u": 137999.99475921688, + "u_angle": -4.64522124982361e-08 + }, + { + "id": 2, + "u_pu": 1.0000102843613505, + "u": 69000.7096209332, + "u_angle": 0.5236134767316929 + }, + { + "id": 3, + "u_pu": 1.0000102843613505, + "u": 13800.141924186639, + "u_angle": 0.5236134767316928 + } + ], + "three_winding_transformer": [ + { + "id": 4, + "loading": 0.0010000050851882336, + "p_1": 50000.49882160645, + "q_1": 33166.04786583574, + "i_1": 0.2510231421958197, + "p_2": -3.808758835315694e-09, + "q_2": 6.343929592691402e-08, + "i_2": 5.317720159164299e-13, + "p_3": 1.0402603884193392e-08, + "q_3": 3.8825329323211554e-08, + "i_3": 1.6816107662352306e-12 + } + ] + }, + { + "node": [ + { + "id": 1, + "u_pu": 0.9999999634979918, + "u": 137999.99496272288, + "u_angle": -4.464841654624962e-08 + }, + { + "id": 2, + "u_pu": 0.9804022410157534, + "u": 67647.75463008697, + "u_angle": 0.5236134785354888 + }, + { + "id": 3, + "u_pu": 0.9804022410157534, + "u": 13529.550926017397, + "u_angle": 0.5236134785354887 + } + ], + "three_winding_transformer": [ + { + "id": 4, + "loading": 0.0009611736721284816, + "p_1": 48058.91865627196, + "q_1": 31878.169934368165, + "i_1": 0.24127560808589638, + "p_2": -4.826267963601246e-08, + "q_2": -2.786541722522586e-08, + "i_2": 4.756313504484951e-13, + "p_3": -7.612809686123832e-08, + "q_3": 2.0397262410786612e-08, + "i_3": 3.3632215324704615e-12 + } + ] + }, + { + "node": [ + { + "id": 1, + "u_pu": 0.9999999648884088, + "u": 137999.99515460042, + "u_angle": -4.2947681268180845e-08 + }, + { + "id": 2, + "u_pu": 0.9615483531069576, + "u": 66346.83636438008, + "u_angle": 0.5236134802362222 + }, + { + "id": 3, + "u_pu": 0.9615483531069579, + "u": 13269.36727287602, + "u_angle": 0.5236134802362223 + } + ], + "three_winding_transformer": [ + { + "id": 4, + "loading": 0.0009245609898850878, + "p_1": 46228.27228508022, + "q_1": 30663.883120028022, + "i_1": 0.23208502397164937, + "p_2": -4.733455120103244e-08, + "q_2": -2.7329543935680792e-08, + "i_2": 4.75631350448495e-13, + "p_3": 1.0002503632675822e-08, + "q_3": 3.7332047568356624e-08, + "i_3": 1.6816107662352306e-12 + } + ] + }, + { + "node": [ + { + "id": 1, + "u_pu": 0.9999999662008815, + "u": 137999.99533572164, + "u_angle": -4.134230393536602e-08 + }, + { + "id": 2, + "u_pu": 0.9434059325839288, + "u": 65095.00934829109, + "u_angle": 0.5236134818416015 + }, + { + "id": 3, + "u_pu": 0.9434059325839287, + "u": 13019.001869658217, + "u_angle": 0.5236134818416013 + } + ], + "three_winding_transformer": [ + { + "id": 4, + "loading": 0.0008900009732490922, + "p_1": 44500.26632201043, + "q_1": 29517.66477643068, + "i_1": 0.22340970384861286, + "p_2": -2.6813892272930526e-08, + "q_2": 4.6441446479076033e-08, + "i_2": 4.756313504484951e-13, + "p_3": -2.3220723239538017e-08, + "q_3": -1.3406946136465258e-08, + "i_3": 1.1890783761212376e-12 + } + ] + }, + { + "node": [ + { + "id": 1, + "u_pu": 0.99999996744111, + "u": 137999.99550687318, + "u_angle": -3.982528529166713e-08 + }, + { + "id": 2, + "u_pu": 0.9259354534992644, + "u": 63889.546291449245, + "u_angle": 0.5236134833586201 + }, + { + "id": 3, + "u_pu": 0.9259354534992644, + "u": 12777.909258289848, + "u_angle": 0.5236134833586199 + } + ], + "three_winding_transformer": [ + { + "id": 4, + "loading": 0.000857343189313861, + "p_1": 42867.36913870318, + "q_1": 28434.540658721107, + "i_1": 0.21521188576282096, + "p_2": 0.0, + "q_2": 0.0, + "i_2": 0.0, + "p_3": -3.594937926157101e-08, + "q_3": 9.632040447462946e-09, + "i_3": 1.6816107662352302e-12 + } + ] + }, + { + "node": [ + { + "id": 1, + "u_pu": 0.9999999686143065, + "u": 137999.9956687743, + "u_angle": -3.839025853543182e-08 + }, + { + "id": 2, + "u_pu": 0.9091002645021947, + "u": 62727.91825065143, + "u_angle": 0.5236134847936469 + }, + { + "id": 3, + "u_pu": 0.9091002645021947, + "u": 12545.583650130287, + "u_angle": 0.5236134847936469 + } + ], + "three_winding_transformer": [ + { + "id": 4, + "loading": 0.0008264504945174143, + "p_1": 41322.726843526216, + "q_1": 27409.95727491015, + "i_1": 0.20745714367706827, + "p_2": 0.0, + "q_2": 0.0, + "i_2": 0.0, + "p_3": 3.529575423906768e-08, + "q_3": -9.456912399771713e-09, + "i_3": 1.6816107662352302e-12 + } + ] + } +] \ No newline at end of file diff --git a/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/sym_output_batch.json.license b/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/sym_output_batch.json.license new file mode 100644 index 0000000000..ebe16ce854 --- /dev/null +++ b/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/sym_output_batch.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project + +SPDX-License-Identifier: MPL-2.0 \ No newline at end of file diff --git a/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/update_batch.json b/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/update_batch.json new file mode 100644 index 0000000000..1e20b565e3 --- /dev/null +++ b/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/update_batch.json @@ -0,0 +1,112 @@ +[ + { + "three_winding_transformer": [ + { + "id": 4, + "status_1": 1, + "status_2": 1, + "status_3": 1, + "tap_pos": -8 + } + ] + }, + { + "three_winding_transformer": [ + { + "id": 4, + "status_1": 1, + "status_2": 1, + "status_3": 1, + "tap_pos": -6 + } + ] + }, + { + "three_winding_transformer": [ + { + "id": 4, + "status_1": 1, + "status_2": 1, + "status_3": 1, + "tap_pos": -4 + } + ] + }, + { + "three_winding_transformer": [ + { + "id": 4, + "status_1": 1, + "status_2": 1, + "status_3": 1, + "tap_pos": -2 + } + ] + }, + { + "three_winding_transformer": [ + { + "id": 4, + "status_1": 1, + "status_2": 1, + "status_3": 1, + "tap_pos": 0 + } + ] + }, + { + "three_winding_transformer": [ + { + "id": 4, + "status_1": 1, + "status_2": 1, + "status_3": 1, + "tap_pos": 2 + } + ] + }, + { + "three_winding_transformer": [ + { + "id": 4, + "status_1": 1, + "status_2": 1, + "status_3": 1, + "tap_pos": 4 + } + ] + }, + { + "three_winding_transformer": [ + { + "id": 4, + "status_1": 1, + "status_2": 1, + "status_3": 1, + "tap_pos": 6 + } + ] + }, + { + "three_winding_transformer": [ + { + "id": 4, + "status_1": 1, + "status_2": 1, + "status_3": 1, + "tap_pos": 8 + } + ] + }, + { + "three_winding_transformer": [ + { + "id": 4, + "status_1": 1, + "status_2": 1, + "status_3": 1, + "tap_pos": 10 + } + ] + } +] \ No newline at end of file diff --git a/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/update_batch.json.license b/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/update_batch.json.license new file mode 100644 index 0000000000..ebe16ce854 --- /dev/null +++ b/tests/data/power_flow/pandapower/components/symmetric/three_winding_transformer/update_batch.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project + +SPDX-License-Identifier: MPL-2.0 \ No newline at end of file diff --git a/tests/data/power_flow/three-winding-transformer/asym_output_batch.json b/tests/data/power_flow/three-winding-transformer/asym_output_batch.json new file mode 100644 index 0000000000..e81659b48c --- /dev/null +++ b/tests/data/power_flow/three-winding-transformer/asym_output_batch.json @@ -0,0 +1,1281 @@ +[ + { + "node": [ + { + "id": 1, + "energized": 1, + "u_pu": [ + 1.0, + 1.0, + 1.0 + ], + "u": [ + 7.967433714816834e+04, + 7.967433714816834e+04, + 7.967433714816834e+04 + ], + "u_angle": [ + 0.0, + -2.094395102393195, + 2.094395102393195 + ] + }, + { + "id": 2, + "energized": 1, + "u_pu": [ + 1.07009926, + 1.07009926, + 1.07009926 + ], + "u": [ + 42629.72443567, + 42629.72443567, + 42629.72443567 + ], + "u_angle": [ + 0.523598775598299, + -1.570796326794897, + 2.617993877991494 + ] + }, + { + "id": 3, + "energized": 1, + "u_pu": [ + 1.07009926, + 1.07009926, + 1.07009926 + ], + "u": [ + 8525.94488713, + 8525.94488713, + 8525.94488713 + ], + "u_angle": [ + 0.523598775598299, + -1.570796326794897, + 2.617993877991494 + ] + } + ], + "three_winding_transformer": [ + { + "id": 4, + "energized": 1, + "loading": 0.0, + "p_1": [ + 0.0, + 0.0, + 0.0 + ], + "q_1": [ + 0.0, + 0.0, + 0.0 + ], + "i_1": [ + 0.0, + 0.0, + 0.0 + ], + "s_1": [ + 0.0, + 0.0, + 0.0 + ], + "p_2": [ + 0.0, + 0.0, + 0.0 + ], + "q_2": [ + 0.0, + 0.0, + 0.0 + ], + "i_2": [ + 0.0, + 0.0, + 0.0 + ], + "s_2": [ + 0.0, + 0.0, + 0.0 + ], + "p_3": [ + 0.0, + 0.0, + 0.0 + ], + "q_3": [ + 0.0, + 0.0, + 0.0 + ], + "i_3": [ + 0.0, + 0.0, + 0.0 + ], + "s_3": [ + 0.0, + 0.0, + 0.0 + ] + } + ] + }, { + "node": [ + { + "id": 1, + "energized": 1, + "u_pu": [ + 1.0, + 1.0, + 1.0 + ], + "u": [ + 7.967433714816834e+04, + 7.967433714816834e+04, + 7.967433714816834e+04 + ], + "u_angle": [ + 0.0, + -2.094395102393195, + 2.094395102393195 + ] + }, + { + "id": 2, + "energized": 1, + "u_pu": [ + 1.04767689, + 1.04767689, + 1.04767689 + ], + "u": [ + 41736.48089298, + 41736.48089298, + 41736.48089298 + ], + "u_angle": [ + 0.523598775598299, + -1.570796326794897, + 2.617993877991494 + ] + }, + { + "id": 3, + "energized": 1, + "u_pu": [ + 1.04767689, + 1.04767689, + 1.04767689 + ], + "u": [ + 8347.2961786, + 8347.2961786, + 8347.2961786 + ], + "u_angle": [ + 0.523598775598299, + -1.570796326794897, + 2.617993877991494 + ] + } + ], + "three_winding_transformer": [ + { + "id": 4, + "energized": 1, + "loading": 0.0, + "p_1": [ + 0.0, + 0.0, + 0.0 + ], + "q_1": [ + 0.0, + 0.0, + 0.0 + ], + "i_1": [ + 0.0, + 0.0, + 0.0 + ], + "s_1": [ + 0.0, + 0.0, + 0.0 + ], + "p_2": [ + 0.0, + 0.0, + 0.0 + ], + "q_2": [ + 0.0, + 0.0, + 0.0 + ], + "i_2": [ + 0.0, + 0.0, + 0.0 + ], + "s_2": [ + 0.0, + 0.0, + 0.0 + ], + "p_3": [ + 0.0, + 0.0, + 0.0 + ], + "q_3": [ + 0.0, + 0.0, + 0.0 + ], + "i_3": [ + 0.0, + 0.0, + 0.0 + ], + "s_3": [ + 0.0, + 0.0, + 0.0 + ] + } + ] + }, + { + "node": [ + { + "id": 1, + "energized": 1, + "u_pu": [ + 1.0, + 1.0, + 1.0 + ], + "u": [ + 7.967433714816834e+04, + 7.967433714816834e+04, + 7.967433714816834e+04 + ], + "u_angle": [ + 0.0, + -2.094395102393195, + 2.094395102393195 + ] + }, + { + "id": 2, + "energized": 1, + "u_pu": [ + 1.0261749, + 1.0261749, + 1.0261749 + ], + "u": [ + 40879.90231427, + 40879.90231427, + 40879.90231427 + ], + "u_angle": [ + 0.523598775598299, + -1.570796326794897, + 2.617993877991494 + ] + }, + { + "id": 3, + "energized": 1, + "u_pu": [ + 1.0261749, + 1.0261749, + 1.0261749 + ], + "u": [ + 8175.98046285, + 8175.98046285, + 8175.98046285 + ], + "u_angle": [ + 0.523598775598299, + -1.570796326794897, + 2.617993877991494 + ] + } + ], + "three_winding_transformer": [ + { + "id": 4, + "energized": 1, + "loading": 0.0, + "p_1": [ + 0.0, + 0.0, + 0.0 + ], + "q_1": [ + 0.0, + 0.0, + 0.0 + ], + "i_1": [ + 0.0, + 0.0, + 0.0 + ], + "s_1": [ + 0.0, + 0.0, + 0.0 + ], + "p_2": [ + 0.0, + 0.0, + 0.0 + ], + "q_2": [ + 0.0, + 0.0, + 0.0 + ], + "i_2": [ + 0.0, + 0.0, + 0.0 + ], + "s_2": [ + 0.0, + 0.0, + 0.0 + ], + "p_3": [ + 0.0, + 0.0, + 0.0 + ], + "q_3": [ + 0.0, + 0.0, + 0.0 + ], + "i_3": [ + 0.0, + 0.0, + 0.0 + ], + "s_3": [ + 0.0, + 0.0, + 0.0 + ] + } + ] + }, + { + "node": [ + { + "id": 1, + "energized": 1, + "u_pu": [ + 1.0, + 1.0, + 1.0 + ], + "u": [ + 7.967433714816834e+04, + 7.967433714816834e+04, + 7.967433714816834e+04 + ], + "u_angle": [ + 0.0, + -2.094395102393195, + 2.094395102393195 + ] + }, + { + "id": 2, + "energized": 1, + "u_pu": [ + 1.00553774, + 1.00553774, + 1.00553774 + ], + "u": [ + 40057.77661923, + 40057.77661923, + 40057.77661923 + ], + "u_angle": [ + 0.523598775598299, + -1.570796326794897, + 2.617993877991494 + ] + }, + { + "id": 3, + "energized": 1, + "u_pu": [ + 1.00553774, + 1.00553774, + 1.00553774 + ], + "u": [ + 8011.55532385, + 8011.55532385, + 8011.55532385 + ], + "u_angle": [ + 0.523598775598299, + -1.570796326794897, + 2.617993877991494 + ] + } + ], + "three_winding_transformer": [ + { + "id": 4, + "energized": 1, + "loading": 0.0, + "p_1": [ + 0.0, + 0.0, + 0.0 + ], + "q_1": [ + 0.0, + 0.0, + 0.0 + ], + "i_1": [ + 0.0, + 0.0, + 0.0 + ], + "s_1": [ + 0.0, + 0.0, + 0.0 + ], + "p_2": [ + 0.0, + 0.0, + 0.0 + ], + "q_2": [ + 0.0, + 0.0, + 0.0 + ], + "i_2": [ + 0.0, + 0.0, + 0.0 + ], + "s_2": [ + 0.0, + 0.0, + 0.0 + ], + "p_3": [ + 0.0, + 0.0, + 0.0 + ], + "q_3": [ + 0.0, + 0.0, + 0.0 + ], + "i_3": [ + 0.0, + 0.0, + 0.0 + ], + "s_3": [ + 0.0, + 0.0, + 0.0 + ] + } + ] + }, + { + "node": [ + { + "id": 1, + "energized": 1, + "u_pu": [ + 1.0, + 1.0, + 1.0 + ], + "u": [ + 7.967433714816834e+04, + 7.967433714816834e+04, + 7.967433714816834e+04 + ], + "u_angle": [ + 0.0, + -2.094395102393195, + 2.094395102393195 + ] + }, + { + "id": 2, + "energized": 1, + "u_pu": [ + 0.98571429, + 0.98571429, + 0.98571429 + ], + "u": [ + 39268.06616588, + 39268.06616588, + 39268.06616588 + ], + "u_angle": [ + 0.523598775598299, + -1.570796326794897, + 2.617993877991494 + ] + }, + { + "id": 3, + "energized": 1, + "u_pu": [ + 0.98571429, + 0.98571429, + 0.98571429 + ], + "u": [ + 7853.61323318, + 7853.61323318, + 7853.61323318 + ], + "u_angle": [ + 0.523598775598299, + -1.570796326794897, + 2.617993877991494 + ] + } + ], + "three_winding_transformer": [ + { + "id": 4, + "energized": 1, + "loading": 0.0, + "p_1": [ + 0.0, + 0.0, + 0.0 + ], + "q_1": [ + 0.0, + 0.0, + 0.0 + ], + "i_1": [ + 0.0, + 0.0, + 0.0 + ], + "s_1": [ + 0.0, + 0.0, + 0.0 + ], + "p_2": [ + 0.0, + 0.0, + 0.0 + ], + "q_2": [ + 0.0, + 0.0, + 0.0 + ], + "i_2": [ + 0.0, + 0.0, + 0.0 + ], + "s_2": [ + 0.0, + 0.0, + 0.0 + ], + "p_3": [ + 0.0, + 0.0, + 0.0 + ], + "q_3": [ + 0.0, + 0.0, + 0.0 + ], + "i_3": [ + 0.0, + 0.0, + 0.0 + ], + "s_3": [ + 0.0, + 0.0, + 0.0 + ] + } + ] + }, + { + "node": [ + { + "id": 1, + "energized": 1, + "u_pu": [ + 1.0, + 1.0, + 1.0 + ], + "u": [ + 7.967433714816834e+04, + 7.967433714816834e+04, + 7.967433714816834e+04 + ], + "u_angle": [ + 0.0, + -2.094395102393195, + 2.094395102393195 + ] + }, + { + "id": 2, + "energized": 1, + "u_pu": [ + 0.96665733, + 0.96665733, + 0.96665733 + ], + "u": [ + 38508.89088837, + 38508.89088837, + 38508.89088837 + ], + "u_angle": [ + 0.523598775598299, + -1.570796326794897, + 2.617993877991494 + ] + }, + { + "id": 3, + "energized": 1, + "u_pu": [ + 0.96665733, + 0.96665733, + 0.96665733 + ], + "u": [ + 7701.77817767, + 7701.77817767, + 7701.77817767 + ], + "u_angle": [ + 0.523598775598299, + -1.570796326794897, + 2.617993877991494 + ] + } + ], + "three_winding_transformer": [ + { + "id": 4, + "energized": 1, + "loading": 0.0, + "p_1": [ + 0.0, + 0.0, + 0.0 + ], + "q_1": [ + 0.0, + 0.0, + 0.0 + ], + "i_1": [ + 0.0, + 0.0, + 0.0 + ], + "s_1": [ + 0.0, + 0.0, + 0.0 + ], + "p_2": [ + 0.0, + 0.0, + 0.0 + ], + "q_2": [ + 0.0, + 0.0, + 0.0 + ], + "i_2": [ + 0.0, + 0.0, + 0.0 + ], + "s_2": [ + 0.0, + 0.0, + 0.0 + ], + "p_3": [ + 0.0, + 0.0, + 0.0 + ], + "q_3": [ + 0.0, + 0.0, + 0.0 + ], + "i_3": [ + 0.0, + 0.0, + 0.0 + ], + "s_3": [ + 0.0, + 0.0, + 0.0 + ] + } + ] + }, + { + "node": [ + { + "id": 1, + "energized": 1, + "u_pu": [ + 1.0, + 1.0, + 1.0 + ], + "u": [ + 7.967433714816834e+04, + 7.967433714816834e+04, + 7.967433714816834e+04 + ], + "u_angle": [ + 0.0, + -2.094395102393195, + 2.094395102393195 + ] + }, + { + "id": 2, + "energized": 1, + "u_pu": [ + 0.94832325, + 0.94832325, + 0.94832325 + ], + "u": [ + 37778.51335365, + 37778.51335365, + 37778.51335365 + ], + "u_angle": [ + 0.523598775598299, + -1.570796326794897, + 2.617993877991494 + ] + }, + { + "id": 3, + "energized": 1, + "u_pu": [ + 0.94832325, + 0.94832325, + 0.94832325 + ], + "u": [ + 7555.70267073, + 7555.70267073, + 7555.70267073 + ], + "u_angle": [ + 0.523598775598299, + -1.570796326794897, + 2.617993877991494 + ] + } + ], + "three_winding_transformer": [ + { + "id": 4, + "energized": 1, + "loading": 0.0, + "p_1": [ + 0.0, + 0.0, + 0.0 + ], + "q_1": [ + 0.0, + 0.0, + 0.0 + ], + "i_1": [ + 0.0, + 0.0, + 0.0 + ], + "s_1": [ + 0.0, + 0.0, + 0.0 + ], + "p_2": [ + 0.0, + 0.0, + 0.0 + ], + "q_2": [ + 0.0, + 0.0, + 0.0 + ], + "i_2": [ + 0.0, + 0.0, + 0.0 + ], + "s_2": [ + 0.0, + 0.0, + 0.0 + ], + "p_3": [ + 0.0, + 0.0, + 0.0 + ], + "q_3": [ + 0.0, + 0.0, + 0.0 + ], + "i_3": [ + 0.0, + 0.0, + 0.0 + ], + "s_3": [ + 0.0, + 0.0, + 0.0 + ] + } + ] + }, + { + "node": [ + { + "id": 1, + "energized": 1, + "u_pu": [ + 1.0, + 1.0, + 1.0 + ], + "u": [ + 7.967433714816834e+04, + 7.967433714816834e+04, + 7.967433714816834e+04 + ], + "u_angle": [ + 0.0, + -2.094395102393195, + 2.094395102393195 + ] + }, + { + "id": 2, + "energized": 1, + "u_pu": [ + 0.9306717, + 0.9306717, + 0.9306717 + ], + "u": [ + 37075.32548708, + 37075.32548708, + 37075.32548708 + ], + "u_angle": [ + 0.523598775598299, + -1.570796326794897, + 2.617993877991494 + ] + }, + { + "id": 3, + "energized": 1, + "u_pu": [ + 0.9306717, + 0.9306717, + 0.9306717 + ], + "u": [ + 7415.06509742, + 7415.06509742, + 7415.06509742 + ], + "u_angle": [ + 0.523598775598299, + -1.570796326794897, + 2.617993877991494 + ] + } + ], + "three_winding_transformer": [ + { + "id": 4, + "energized": 1, + "loading": 0.0, + "p_1": [ + 0.0, + 0.0, + 0.0 + ], + "q_1": [ + 0.0, + 0.0, + 0.0 + ], + "i_1": [ + 0.0, + 0.0, + 0.0 + ], + "s_1": [ + 0.0, + 0.0, + 0.0 + ], + "p_2": [ + 0.0, + 0.0, + 0.0 + ], + "q_2": [ + 0.0, + 0.0, + 0.0 + ], + "i_2": [ + 0.0, + 0.0, + 0.0 + ], + "s_2": [ + 0.0, + 0.0, + 0.0 + ], + "p_3": [ + 0.0, + 0.0, + 0.0 + ], + "q_3": [ + 0.0, + 0.0, + 0.0 + ], + "i_3": [ + 0.0, + 0.0, + 0.0 + ], + "s_3": [ + 0.0, + 0.0, + 0.0 + ] + } + ] + }, + { + "node": [ + { + "id": 1, + "energized": 1, + "u_pu": [ + 1.0, + 1.0, + 1.0 + ], + "u": [ + 7.967433714816834e+04, + 7.967433714816834e+04, + 7.967433714816834e+04 + ], + "u_angle": [ + 0.0, + -2.094395102393195, + 2.094395102393195 + ] + }, + { + "id": 2, + "energized": 1, + "u_pu": [ + 0.91366525, + 0.91366525, + 0.91366525 + ], + "u": [ + 36397.83675333, + 36397.83675333, + 36397.83675333 + ], + "u_angle": [ + 0.523598775598299, + -1.570796326794897, + 2.617993877991494 + ] + }, + { + "id": 3, + "energized": 1, + "u_pu": [ + 0.91366525, + 0.91366525, + 0.91366525 + ], + "u": [ + 7279.56735067, + 7279.56735067, + 7279.56735067 + ], + "u_angle": [ + 0.523598775598299, + -1.570796326794897, + 2.617993877991494 + ] + } + ], + "three_winding_transformer": [ + { + "id": 4, + "energized": 1, + "loading": 0.0, + "p_1": [ + 0.0, + 0.0, + 0.0 + ], + "q_1": [ + 0.0, + 0.0, + 0.0 + ], + "i_1": [ + 0.0, + 0.0, + 0.0 + ], + "s_1": [ + 0.0, + 0.0, + 0.0 + ], + "p_2": [ + 0.0, + 0.0, + 0.0 + ], + "q_2": [ + 0.0, + 0.0, + 0.0 + ], + "i_2": [ + 0.0, + 0.0, + 0.0 + ], + "s_2": [ + 0.0, + 0.0, + 0.0 + ], + "p_3": [ + 0.0, + 0.0, + 0.0 + ], + "q_3": [ + 0.0, + 0.0, + 0.0 + ], + "i_3": [ + 0.0, + 0.0, + 0.0 + ], + "s_3": [ + 0.0, + 0.0, + 0.0 + ] + } + ] + }, + { + "node": [ + { + "id": 1, + "energized": 1, + "u_pu": [ + 1.0, + 1.0, + 1.0 + ], + "u": [ + 7.967433714816834e+04, + 7.967433714816834e+04, + 7.967433714816834e+04 + ], + "u_angle": [ + 0.0, + -2.094395102393195, + 2.094395102393195 + ] + }, + { + "id": 2, + "energized": 1, + "u_pu": [ + 0.89726918, + 0.89726918, + 0.89726918 + ], + "u": [ + 35744.66361004, + 35744.66361004, + 35744.66361004 + ], + "u_angle": [ + 0.523598775598299, + -1.570796326794897, + 2.617993877991494 + ] + }, + { + "id": 3, + "energized": 1, + "u_pu": [ + 0.89726918, + 0.89726918, + 0.89726918 + ], + "u": [ + 7148.93272201, + 7148.93272201, + 7148.93272201 + ], + "u_angle": [ + 0.523598775598299, + -1.570796326794897, + 2.617993877991494 + ] + } + ], + "three_winding_transformer": [ + { + "id": 4, + "energized": 1, + "loading": 0.0, + "p_1": [ + 0.0, + 0.0, + 0.0 + ], + "q_1": [ + 0.0, + 0.0, + 0.0 + ], + "i_1": [ + 0.0, + 0.0, + 0.0 + ], + "s_1": [ + 0.0, + 0.0, + 0.0 + ], + "p_2": [ + 0.0, + 0.0, + 0.0 + ], + "q_2": [ + 0.0, + 0.0, + 0.0 + ], + "i_2": [ + 0.0, + 0.0, + 0.0 + ], + "s_2": [ + 0.0, + 0.0, + 0.0 + ], + "p_3": [ + 0.0, + 0.0, + 0.0 + ], + "q_3": [ + 0.0, + 0.0, + 0.0 + ], + "i_3": [ + 0.0, + 0.0, + 0.0 + ], + "s_3": [ + 0.0, + 0.0, + 0.0 + ] + } + ] + } +] \ No newline at end of file diff --git a/tests/data/power_flow/three-winding-transformer/asym_output_batch.json.license b/tests/data/power_flow/three-winding-transformer/asym_output_batch.json.license new file mode 100644 index 0000000000..ebe16ce854 --- /dev/null +++ b/tests/data/power_flow/three-winding-transformer/asym_output_batch.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project + +SPDX-License-Identifier: MPL-2.0 \ No newline at end of file diff --git a/tests/data/power_flow/three-winding-transformer/input.json b/tests/data/power_flow/three-winding-transformer/input.json new file mode 100644 index 0000000000..c801e55e75 --- /dev/null +++ b/tests/data/power_flow/three-winding-transformer/input.json @@ -0,0 +1,63 @@ +{ + "node": [ + { + "id": 1, + "u_rated": 138000.0 + }, + { + "id": 2, + "u_rated": 69000.0 + }, + { + "id": 3, + "u_rated": 13800.0 + } + ], + "three_winding_transformer": [ + { + "id": 4, + "node_1": 1, + "node_2": 2, + "node_3": 3, + "status_1": 0, + "status_2": 0, + "status_3": 0, + "u1": 140000.0, + "u2": 69000.0, + "u3": 13800.0, + "sn_1": 60000000.0, + "sn_2": 50000000.0, + "sn_3": 10000000.0, + "uk_12": 0.09, + "uk_13": 0.06, + "uk_23": 0.03, + "pk_12": 50000.0, + "pk_13": 10000.0, + "pk_23": 8000.0, + "i0": 0.0, + "p0": 0.0, + "winding_1": 2, + "winding_2": 1, + "winding_3": 1, + "clock_12": 11, + "clock_13": 11, + "tap_side": 0, + "tap_pos": 0, + "tap_min": -10, + "tap_max": 10, + "tap_nom": 0, + "tap_size": 1380.0 + } + ], + "source": [ + { + "id": 7, + "node": 1, + "status": 1, + "u_ref": 1.0, + "sk": 1000000000000.0, + "rx_ratio": 0.1, + "z01_ratio": 1.0 + } + ] +} \ No newline at end of file diff --git a/tests/data/power_flow/three-winding-transformer/input.json.license b/tests/data/power_flow/three-winding-transformer/input.json.license new file mode 100644 index 0000000000..ebe16ce854 --- /dev/null +++ b/tests/data/power_flow/three-winding-transformer/input.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project + +SPDX-License-Identifier: MPL-2.0 \ No newline at end of file diff --git a/tests/data/power_flow/three-winding-transformer/params.json b/tests/data/power_flow/three-winding-transformer/params.json new file mode 100644 index 0000000000..555ad7a250 --- /dev/null +++ b/tests/data/power_flow/three-winding-transformer/params.json @@ -0,0 +1,7 @@ +{ + "calculation_method": ["newton_raphson", "iterative_current"], + "rtol": 1e-04, + "atol": 1e-05, + "independent": true, + "cache_topology": false +} \ No newline at end of file diff --git a/tests/data/power_flow/three-winding-transformer/params.json.license b/tests/data/power_flow/three-winding-transformer/params.json.license new file mode 100644 index 0000000000..ebe16ce854 --- /dev/null +++ b/tests/data/power_flow/three-winding-transformer/params.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project + +SPDX-License-Identifier: MPL-2.0 \ No newline at end of file diff --git a/tests/data/power_flow/three-winding-transformer/sym_output_batch.json b/tests/data/power_flow/three-winding-transformer/sym_output_batch.json new file mode 100644 index 0000000000..7e7670eb19 --- /dev/null +++ b/tests/data/power_flow/three-winding-transformer/sym_output_batch.json @@ -0,0 +1,372 @@ +[ + { + "node": [ + { + "id": 1, + "u_pu": 1.0, + "u": 138000.0, + "u_angle": -7.392151529635133e-18 + }, + { + "id": 2, + "u_pu": 1.0700992555831268, + "u": 73836.84863523576, + "u_angle": 0.5235987755982993 + }, + { + "id": 3, + "u_pu": 1.0700992555831268, + "u": 14767.36972704715, + "u_angle": 0.5235987755982991 + } + ], + "three_winding_transformer": [ + { + "id": 4, + "loading": 9.732496033240084e-15, + "p_1": 4.618527782440651e-08, + "q_1": -5.6843418860808015e-08, + "i_1": 3.0641848386786175e-13, + "p_2": 2.4331240083100226e-07, + "q_2": -4.214294403508595e-07, + "i_2": 3.80505080358796e-12, + "p_3": 4.074710081946558e-09, + "q_3": -6.788570509579508e-08, + "i_3": 2.65886007958215e-12 + } + ] + }, + { + "node": [ + { + "id": 1, + "u_pu": 1.0, + "u": 138000.0, + "u_angle": -4.251434640259467e-18 + }, + { + "id": 2, + "u_pu": 1.0476768903735196, + "u": 72289.70543577286, + "u_angle": 0.5235987755982996 + }, + { + "id": 3, + "u_pu": 1.0476768903735199, + "u": 14457.941087154575, + "u_angle": 0.5235987755982996 + } + ], + "three_winding_transformer": [ + { + "id": 4, + "loading": 0.0, + "p_1": 0.0, + "q_1": 0.0, + "i_1": 0.0, + "p_2": 0.0, + "q_2": 0.0, + "i_2": 0.0, + "p_3": 0.0, + "q_3": 0.0, + "i_3": 0.0 + } + ] + }, + { + "node": [ + { + "id": 1, + "u_pu": 1.0, + "u": 138000.0, + "u_angle": 6.745427021506159e-18 + }, + { + "id": 2, + "u_pu": 1.026174895895291, + "u": 70806.06781677509, + "u_angle": 0.5235987755983077 + }, + { + "id": 3, + "u_pu": 1.0261748958952912, + "u": 14161.213563355019, + "u_angle": 0.5235987755983075 + } + ], + "three_winding_transformer": [ + { + "id": 4, + "loading": 0.0, + "p_1": 0.0, + "q_1": 0.0, + "i_1": 0.0, + "p_2": 0.0, + "q_2": 0.0, + "i_2": 0.0, + "p_3": 0.0, + "q_3": 0.0, + "i_3": 0.0 + } + ] + }, + { + "node": [ + { + "id": 1, + "u_pu": 0.9999999999999999, + "u": 137999.99999999997, + "u_angle": 8.294783265050233e-18 + }, + { + "id": 2, + "u_pu": 1.005537744097936, + "u": 69382.10434275758, + "u_angle": 0.5235987755982993 + }, + { + "id": 3, + "u_pu": 1.0055377440979363, + "u": 13876.420868551522, + "u_angle": 0.5235987755982991 + } + ], + "three_winding_transformer": [ + { + "id": 4, + "loading": 0.0, + "p_1": 0.0, + "q_1": 0.0, + "i_1": 0.0, + "p_2": 0.0, + "q_2": 0.0, + "i_2": 0.0, + "p_3": 0.0, + "q_3": 0.0, + "i_3": 0.0 + } + ] + }, + { + "node": [ + { + "id": 1, + "u_pu": 1.0, + "u": 138000.0, + "u_angle": -5.779229818053184e-18 + }, + { + "id": 2, + "u_pu": 0.9857142857143273, + "u": 68014.28571428858, + "u_angle": 0.5235987755982987 + }, + { + "id": 3, + "u_pu": 0.9857142857143276, + "u": 13602.857142857722, + "u_angle": 0.5235987755982985 + } + ], + "three_winding_transformer": [ + { + "id": 4, + "loading": 0.0, + "p_1": 0.0, + "q_1": 0.0, + "i_1": 0.0, + "p_2": 0.0, + "q_2": 0.0, + "i_2": 0.0, + "p_3": 0.0, + "q_3": 0.0, + "i_3": 0.0 + } + ] + }, + { + "node": [ + { + "id": 1, + "u_pu": 1.0000000000000002, + "u": 138000.00000000003, + "u_angle": -1.2815867477037476e-17 + }, + { + "id": 2, + "u_pu": 0.9666573269827555, + "u": 66699.35556181014, + "u_angle": 0.5235987755982942 + }, + { + "id": 3, + "u_pu": 0.9666573269827557, + "u": 13339.871112362029, + "u_angle": 0.523598775598294 + } + ], + "three_winding_transformer": [ + { + "id": 4, + "loading": 0.0, + "p_1": 0.0, + "q_1": 0.0, + "i_1": 0.0, + "p_2": 0.0, + "q_2": 0.0, + "i_2": 0.0, + "p_3": 0.0, + "q_3": 0.0, + "i_3": 0.0 + } + ] + }, + { + "node": [ + { + "id": 1, + "u_pu": 0.9999999999999989, + "u": 137999.99999999985, + "u_angle": 1.1545196650599027e-16 + }, + { + "id": 2, + "u_pu": 0.9483232545324306, + "u": 65434.30456273771, + "u_angle": 0.5235987755983207 + }, + { + "id": 3, + "u_pu": 0.9483232545324308, + "u": 13086.860912547547, + "u_angle": 0.5235987755983207 + } + ], + "three_winding_transformer": [ + { + "id": 4, + "loading": 0.0, + "p_1": 0.0, + "q_1": 0.0, + "i_1": 0.0, + "p_2": 0.0, + "q_2": 0.0, + "i_2": 0.0, + "p_3": 0.0, + "q_3": 0.0, + "i_3": 0.0 + } + ] + }, + { + "node": [ + { + "id": 1, + "u_pu": 0.9999999999999999, + "u": 137999.99999999997, + "u_angle": 1.423130845955364e-17 + }, + { + "id": 2, + "u_pu": 0.9306717021850557, + "u": 64216.34745076884, + "u_angle": 0.5235987755982993 + }, + { + "id": 3, + "u_pu": 0.930671702185056, + "u": 12843.269490153772, + "u_angle": 0.5235987755982991 + } + ], + "three_winding_transformer": [ + { + "id": 4, + "loading": 0.0, + "p_1": 0.0, + "q_1": 0.0, + "i_1": 0.0, + "p_2": 0.0, + "q_2": 0.0, + "i_2": 0.0, + "p_3": 0.0, + "q_3": 0.0, + "i_3": 0.0 + } + ] + }, + { + "node": [ + { + "id": 1, + "u_pu": 1.0, + "u": 138000.0, + "u_angle": 5.482216107817325e-18 + }, + { + "id": 2, + "u_pu": 0.9136652542372882, + "u": 63042.90254237289, + "u_angle": 0.5235987755982996 + }, + { + "id": 3, + "u_pu": 0.9136652542372884, + "u": 12608.580508474579, + "u_angle": 0.5235987755982995 + } + ], + "three_winding_transformer": [ + { + "id": 4, + "loading": 0.0, + "p_1": 0.0, + "q_1": 0.0, + "i_1": 0.0, + "p_2": 0.0, + "q_2": 0.0, + "i_2": 0.0, + "p_3": 0.0, + "q_3": 0.0, + "i_3": 0.0 + } + ] + }, + { + "node": [ + { + "id": 1, + "u_pu": 1.0000000000000002, + "u": 138000.00000000003, + "u_angle": -1.4108354825883456e-17 + }, + { + "id": 2, + "u_pu": 0.8972691807542262, + "u": 61911.57347204161, + "u_angle": 0.5235987755982995 + }, + { + "id": 3, + "u_pu": 0.8972691807542263, + "u": 12382.314694408324, + "u_angle": 0.5235987755982994 + } + ], + "three_winding_transformer": [ + { + "id": 4, + "loading": 0.0, + "p_1": 0.0, + "q_1": 0.0, + "i_1": 0.0, + "p_2": 0.0, + "q_2": 0.0, + "i_2": 0.0, + "p_3": 0.0, + "q_3": 0.0, + "i_3": 0.0 + } + ] + } +] \ No newline at end of file diff --git a/tests/data/power_flow/three-winding-transformer/sym_output_batch.json.license b/tests/data/power_flow/three-winding-transformer/sym_output_batch.json.license new file mode 100644 index 0000000000..ebe16ce854 --- /dev/null +++ b/tests/data/power_flow/three-winding-transformer/sym_output_batch.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project + +SPDX-License-Identifier: MPL-2.0 \ No newline at end of file diff --git a/tests/data/power_flow/three-winding-transformer/update_batch.json b/tests/data/power_flow/three-winding-transformer/update_batch.json new file mode 100644 index 0000000000..1e20b565e3 --- /dev/null +++ b/tests/data/power_flow/three-winding-transformer/update_batch.json @@ -0,0 +1,112 @@ +[ + { + "three_winding_transformer": [ + { + "id": 4, + "status_1": 1, + "status_2": 1, + "status_3": 1, + "tap_pos": -8 + } + ] + }, + { + "three_winding_transformer": [ + { + "id": 4, + "status_1": 1, + "status_2": 1, + "status_3": 1, + "tap_pos": -6 + } + ] + }, + { + "three_winding_transformer": [ + { + "id": 4, + "status_1": 1, + "status_2": 1, + "status_3": 1, + "tap_pos": -4 + } + ] + }, + { + "three_winding_transformer": [ + { + "id": 4, + "status_1": 1, + "status_2": 1, + "status_3": 1, + "tap_pos": -2 + } + ] + }, + { + "three_winding_transformer": [ + { + "id": 4, + "status_1": 1, + "status_2": 1, + "status_3": 1, + "tap_pos": 0 + } + ] + }, + { + "three_winding_transformer": [ + { + "id": 4, + "status_1": 1, + "status_2": 1, + "status_3": 1, + "tap_pos": 2 + } + ] + }, + { + "three_winding_transformer": [ + { + "id": 4, + "status_1": 1, + "status_2": 1, + "status_3": 1, + "tap_pos": 4 + } + ] + }, + { + "three_winding_transformer": [ + { + "id": 4, + "status_1": 1, + "status_2": 1, + "status_3": 1, + "tap_pos": 6 + } + ] + }, + { + "three_winding_transformer": [ + { + "id": 4, + "status_1": 1, + "status_2": 1, + "status_3": 1, + "tap_pos": 8 + } + ] + }, + { + "three_winding_transformer": [ + { + "id": 4, + "status_1": 1, + "status_2": 1, + "status_3": 1, + "tap_pos": 10 + } + ] + } +] \ No newline at end of file diff --git a/tests/data/power_flow/three-winding-transformer/update_batch.json.license b/tests/data/power_flow/three-winding-transformer/update_batch.json.license new file mode 100644 index 0000000000..ebe16ce854 --- /dev/null +++ b/tests/data/power_flow/three-winding-transformer/update_batch.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project + +SPDX-License-Identifier: MPL-2.0 \ No newline at end of file diff --git a/tests/data/state_estimation/three_winding_transformer/input.json b/tests/data/state_estimation/three_winding_transformer/input.json new file mode 100644 index 0000000000..bfdd2f81da --- /dev/null +++ b/tests/data/state_estimation/three_winding_transformer/input.json @@ -0,0 +1,115 @@ +{ + "node": [ + { + "id": 1, + "u_rated": 138000.0 + }, + { + "id": 2, + "u_rated": 69000.0 + }, + { + "id": 3, + "u_rated": 13800.0 + } + ], + "three_winding_transformer": [ + { + "id": 4, + "node_1": 1, + "node_2": 2, + "node_3": 3, + "status_1": 1, + "status_2": 1, + "status_3": 1, + "u1": 138000.0, + "u2": 69000.0, + "u3": 13800.0, + "sn_1": 60000000.0, + "sn_2": 50000000.0, + "sn_3": 10000000.0, + "uk_12": 0.09, + "uk_13": 0.03, + "uk_23": 0.06, + "pk_12": 50000.0, + "pk_13": 5000.0, + "pk_23": 10000.0, + "i0": 0.1, + "p0": 50000.0, + "winding_1": 1, + "winding_2": 2, + "winding_3": 2, + "clock_12": 11, + "clock_13": 11, + "tap_side": 2, + "tap_pos": 0, + "tap_min": -8, + "tap_max": 10, + "tap_nom": 0, + "tap_size": 1380.0 + } + ], + "sym_load": [ + { + "id": 41, + "node": 2, + "status": 1, + "type": 0, + "p_specified": 10000000.0, + "q_specified": 200000.0 + }, + { + "id": 42, + "node": 3, + "status": 1, + "type": 0, + "p_specified": 100000.0, + "q_specified": 20000.0 + } + ], + "source": [ + { + "id": 7, + "node": 1, + "status": 1, + "u_ref": 1.0, + "sk": 1000000000000.0, + "rx_ratio": 0.1, + "z01_ratio": 1.0 + } + ], + "sym_voltage_sensor": [ + { + "id": 71, + "measured_object": 1, + "u_measured": 137998.98120492624, + "u_sigma": 5e3 + } + ], + "sym_power_sensor": [ + { + "id": 61, + "measured_object": 4, + "measured_terminal_type": 6, + "p_measured": 10151892.276210727, + "q_measured": 6404105.027856597, + "power_sigma": 1e5 + }, + { + "id": 62, + "measured_object": 4, + "measured_terminal_type": 7, + "p_measured": -10000000.000000067, + "q_measured": -200000.0000000952, + "power_sigma": 1e5 + }, + { + "id": 63, + "measured_object": 4, + "measured_terminal_type": 8, + "p_measured": -99999.99999998213, + "q_measured": -20000.00000002474, + "power_sigma": 1e5 + } + ] +} \ No newline at end of file diff --git a/tests/data/state_estimation/three_winding_transformer/input.json.license b/tests/data/state_estimation/three_winding_transformer/input.json.license new file mode 100644 index 0000000000..ebe16ce854 --- /dev/null +++ b/tests/data/state_estimation/three_winding_transformer/input.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project + +SPDX-License-Identifier: MPL-2.0 \ No newline at end of file diff --git a/tests/data/state_estimation/three_winding_transformer/params.json b/tests/data/state_estimation/three_winding_transformer/params.json new file mode 100644 index 0000000000..fbd5e00474 --- /dev/null +++ b/tests/data/state_estimation/three_winding_transformer/params.json @@ -0,0 +1,5 @@ +{ + "calculation_method": "iterative_linear", + "rtol": 1e-4, + "atol": 1e-5 +} \ No newline at end of file diff --git a/tests/data/state_estimation/three_winding_transformer/params.json.license b/tests/data/state_estimation/three_winding_transformer/params.json.license new file mode 100644 index 0000000000..ebe16ce854 --- /dev/null +++ b/tests/data/state_estimation/three_winding_transformer/params.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project + +SPDX-License-Identifier: MPL-2.0 \ No newline at end of file diff --git a/tests/data/state_estimation/three_winding_transformer/sym_output.json b/tests/data/state_estimation/three_winding_transformer/sym_output.json new file mode 100644 index 0000000000..350c5e6d26 --- /dev/null +++ b/tests/data/state_estimation/three_winding_transformer/sym_output.json @@ -0,0 +1,46 @@ +{ + "node": [ + { + "id": 1, + "u_pu": 0.9999926174270017, + "u": 137998.98120492624 + }, + { + "id": 2, + "u_pu": 1.0010890743570962, + "u": 69075.14613063964 + }, + { + "id": 3, + "u_pu": 1.002126284942395, + "u": 13829.342732205052 + } + ], + "sym_load": [ + { + "id": 41, + "p": 10000000.0, + "q": 199999.99999999997 + }, + { + "id": 42, + "p": 99999.99999999999, + "q": 20000.0 + } + ], + "three_winding_transformer": [ + { + "id": 4, + "loading": 0.20005101987826696, + "p_1": 10151892.276210727, + "q_1": 6404105.027856597, + "i_1": 50.21754907314644, + "p_2": -10000000.000000067, + "q_2": -200000.0000000952, + "i_2": 83.59963895069126, + "p_3": -99999.99999998213, + "q_3": -20000.00000002474, + "i_3": 4.257498488225235 + } + ] +} \ No newline at end of file diff --git a/tests/data/state_estimation/three_winding_transformer/sym_output.json.license b/tests/data/state_estimation/three_winding_transformer/sym_output.json.license new file mode 100644 index 0000000000..ebe16ce854 --- /dev/null +++ b/tests/data/state_estimation/three_winding_transformer/sym_output.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project + +SPDX-License-Identifier: MPL-2.0 \ No newline at end of file diff --git a/tests/unit/validation/test_input_validation.py b/tests/unit/validation/test_input_validation.py index df97e5b571..d4164d6401 100644 --- a/tests/unit/validation/test_input_validation.py +++ b/tests/unit/validation/test_input_validation.py @@ -7,7 +7,7 @@ import numpy as np import pytest -from power_grid_model import BranchSide, LoadGenType, MeasuredTerminalType, WindingType, initialize_array +from power_grid_model import Branch3Side, BranchSide, LoadGenType, MeasuredTerminalType, WindingType, initialize_array from power_grid_model.validation import validate_input_data from power_grid_model.validation.errors import ( InvalidEnumValueError, @@ -76,6 +76,52 @@ def input_data() -> Dict[str, np.ndarray]: transformer["pk_min"] = [300.0, 0.0, -10.0] transformer["pk_max"] = [400.0, -0.1, -10.0] + three_winding_transformer = initialize_array("input", "three_winding_transformer", 4) + three_winding_transformer["id"] = [1, 28, 29, 30] + three_winding_transformer["node_1"] = [0, 1, 9, 2] + three_winding_transformer["node_2"] = [1, 15, 1, 0] + three_winding_transformer["node_3"] = [1, 2, 12, 1] + three_winding_transformer["status_1"] = [1, 5, 1, 1] + three_winding_transformer["status_2"] = [2, 1, 1, 1] + three_winding_transformer["status_3"] = [1, 0, -1, 0] + three_winding_transformer["u1"] = [-100, 0, 200, 100] + three_winding_transformer["u2"] = [0, -200, 100, 200] + three_winding_transformer["u3"] = [-100, 0, 400, 300] + three_winding_transformer["sn_1"] = [0, -1200, 100, 300] + three_winding_transformer["sn_2"] = [-1000, 0, 200, 200] + three_winding_transformer["sn_3"] = [0, -2300, 300, 100] + three_winding_transformer["uk_12"] = [-1, 1.1, 0.05, 0.1] + three_winding_transformer["uk_13"] = [-2, 1.2, 0.3, 0.2] + three_winding_transformer["uk_23"] = [-1.5, 1, 0.15, 0.2] + three_winding_transformer["pk_12"] = [-450, 100, 10, 40] + three_winding_transformer["pk_13"] = [-40, 50, 40, 50] + three_winding_transformer["pk_23"] = [-120, 1, 40, 30] + three_winding_transformer["i0"] = [-0.5, 1.8, 0.3, 0.6] + three_winding_transformer["p0"] = [-100, 410, 60, 40] + three_winding_transformer["winding_1"] = [15, -1, 0, 2] + three_winding_transformer["winding_2"] = [19, -2, 1, 3] + three_winding_transformer["winding_3"] = [-2, 13, 2, 2] + three_winding_transformer["clock_12"] = [-12, 24, 4, 3] + three_winding_transformer["clock_13"] = [-30, 40, 3, 4] + three_winding_transformer["tap_side"] = [-1, 9, 1, 0] + three_winding_transformer["tap_pos"] = [50, -24, 5, 3] + three_winding_transformer["tap_min"] = [-10, -10, -10, -10] + three_winding_transformer["tap_max"] = [10, 10, 10, 10] + three_winding_transformer["tap_size"] = [-12, 0, 3, 130] + three_winding_transformer["tap_nom"] = [-12, 41, 3, 0] + three_winding_transformer["uk_12_min"] = [-1, 1.1, 0.05, 0.1] + three_winding_transformer["uk_13_min"] = [-2, 1.2, 0.3, 0.2] + three_winding_transformer["uk_23_min"] = [-1.5, 1, 0.15, 0.2] + three_winding_transformer["pk_12_min"] = [-450, 100, 10, 40] + three_winding_transformer["pk_13_min"] = [-40, 50, 40, 50] + three_winding_transformer["pk_23_min"] = [-120, 1, 40, 30] + three_winding_transformer["uk_12_max"] = [-1, 1.1, 0.05, 0.1] + three_winding_transformer["uk_13_max"] = [-2, 1.2, 0.3, 0.2] + three_winding_transformer["uk_23_max"] = [-1.5, 1, 0.15, 0.2] + three_winding_transformer["pk_12_max"] = [-450, 100, 10, 40] + three_winding_transformer["pk_13_max"] = [-40, 50, 40, 50] + three_winding_transformer["pk_23_max"] = [-120, 1, 40, 30] + source = initialize_array("input", "source", 3) source["id"] = [16, 17, 1] source["node"] = [10, 1, 2] @@ -148,6 +194,7 @@ def input_data() -> Dict[str, np.ndarray]: "line": line, "link": link, "transformer": transformer, + "three_winding_transformer": three_winding_transformer, "source": source, "shunt": shunt, "sym_load": sym_load, @@ -180,6 +227,7 @@ def test_validate_input_data_sym_calculation(input_data): ("sym_power_sensor", "id"), ("sym_voltage_sensor", "id"), ("transformer", "id"), + ("three_winding_transformer", "id"), ], [ ("asym_gen", 1), @@ -206,6 +254,7 @@ def test_validate_input_data_sym_calculation(input_data): ("sym_voltage_sensor", 9), ("sym_voltage_sensor", 10), ("transformer", 1), + ("three_winding_transformer", 1), ], ) in validation_errors @@ -342,6 +391,94 @@ def test_validate_input_data_sym_calculation(input_data): assert NotGreaterOrEqualError("transformer", "uk_max", [15], "uk_min") not in validation_errors +def test_validate_three_winding_transformer(input_data): + validation_errors = validate_input_data(input_data, symmetric=True) + assert NotBooleanError("three_winding_transformer", "status_1", [28]) in validation_errors + assert NotBooleanError("three_winding_transformer", "status_2", [1]) in validation_errors + assert NotBooleanError("three_winding_transformer", "status_3", [29]) in validation_errors + assert InvalidIdError("three_winding_transformer", "node_1", [29], "node") in validation_errors + assert InvalidIdError("three_winding_transformer", "node_2", [28], "node") in validation_errors + assert InvalidIdError("three_winding_transformer", "node_3", [29], "node") in validation_errors + assert NotGreaterThanError("three_winding_transformer", "u1", [1, 28], 0) in validation_errors + assert NotGreaterThanError("three_winding_transformer", "u2", [1, 28], 0) in validation_errors + assert NotGreaterThanError("three_winding_transformer", "u3", [1, 28], 0) in validation_errors + assert NotGreaterThanError("three_winding_transformer", "sn_1", [1, 28], 0) in validation_errors + assert NotGreaterThanError("three_winding_transformer", "sn_2", [1, 28], 0) in validation_errors + assert NotGreaterThanError("three_winding_transformer", "sn_3", [1, 28], 0) in validation_errors + assert NotGreaterOrEqualError("three_winding_transformer", "uk_12", [29, 30], "pk_12/sn_1") in validation_errors + assert NotGreaterOrEqualError("three_winding_transformer", "uk_12", [1, 30], "pk_12/sn_2") in validation_errors + assert NotGreaterOrEqualError("three_winding_transformer", "uk_13", [29], "pk_13/sn_1") in validation_errors + assert NotGreaterOrEqualError("three_winding_transformer", "uk_13", [30], "pk_13/sn_3") in validation_errors + assert NotGreaterOrEqualError("three_winding_transformer", "uk_23", [1, 29], "pk_23/sn_2") in validation_errors + assert NotGreaterOrEqualError("three_winding_transformer", "uk_23", [30], "pk_23/sn_3") in validation_errors + assert NotBetweenError("three_winding_transformer", "uk_12", [1, 28], (0, 1)) in validation_errors + assert NotBetweenError("three_winding_transformer", "uk_13", [1, 28], (0, 1)) in validation_errors + assert NotBetweenError("three_winding_transformer", "uk_23", [1, 28], (0, 1)) in validation_errors + assert NotGreaterOrEqualError("three_winding_transformer", "pk_12", [1], 0) in validation_errors + assert NotGreaterOrEqualError("three_winding_transformer", "pk_13", [1], 0) in validation_errors + assert NotGreaterOrEqualError("three_winding_transformer", "pk_23", [1], 0) in validation_errors + assert NotGreaterOrEqualError("three_winding_transformer", "i0", [29], "p0/sn_1") in validation_errors + assert NotLessThanError("three_winding_transformer", "i0", [28], 1) in validation_errors + assert NotGreaterOrEqualError("three_winding_transformer", "p0", [1], 0) in validation_errors + assert NotBetweenOrAtError("three_winding_transformer", "clock_12", [1, 28], (0, 12)) in validation_errors + assert NotBetweenOrAtError("three_winding_transformer", "clock_13", [1, 28], (0, 12)) in validation_errors + assert ( + NotBetweenOrAtError("three_winding_transformer", "tap_pos", [1, 28], ("tap_min", "tap_max")) + in validation_errors + ) + assert ( + NotBetweenOrAtError("three_winding_transformer", "tap_nom", [1, 28], ("tap_min", "tap_max")) + in validation_errors + ) + assert NotGreaterOrEqualError("three_winding_transformer", "tap_size", [1], 0) in validation_errors + assert InvalidEnumValueError("three_winding_transformer", "winding_1", [1, 28], WindingType) in validation_errors + assert InvalidEnumValueError("three_winding_transformer", "winding_2", [1, 28], WindingType) in validation_errors + assert InvalidEnumValueError("three_winding_transformer", "winding_3", [1, 28], WindingType) in validation_errors + assert InvalidEnumValueError("three_winding_transformer", "tap_side", [1, 28], Branch3Side) in validation_errors + + +def test_validate_three_winding_transformer_ukpkminmax(input_data): + validation_errors = validate_input_data(input_data, symmetric=False) + assert ( + NotGreaterOrEqualError("three_winding_transformer", "uk_12_min", [29, 30], "pk_12_min/sn_1") + in validation_errors + ) + assert ( + NotGreaterOrEqualError("three_winding_transformer", "uk_12_min", [1, 30], "pk_12_min/sn_2") in validation_errors + ) + assert NotGreaterOrEqualError("three_winding_transformer", "uk_13_min", [29], "pk_13_min/sn_1") in validation_errors + assert NotGreaterOrEqualError("three_winding_transformer", "uk_13_min", [30], "pk_13_min/sn_3") in validation_errors + assert ( + NotGreaterOrEqualError("three_winding_transformer", "uk_23_min", [1, 29], "pk_23_min/sn_2") in validation_errors + ) + assert NotGreaterOrEqualError("three_winding_transformer", "uk_23_min", [30], "pk_23_min/sn_3") in validation_errors + assert NotBetweenError("three_winding_transformer", "uk_12_min", [1, 28], (0, 1)) in validation_errors + assert NotBetweenError("three_winding_transformer", "uk_13_min", [1, 28], (0, 1)) in validation_errors + assert NotBetweenError("three_winding_transformer", "uk_23_min", [1, 28], (0, 1)) in validation_errors + assert ( + NotGreaterOrEqualError("three_winding_transformer", "uk_12_max", [29, 30], "pk_12_max/sn_1") + in validation_errors + ) + assert ( + NotGreaterOrEqualError("three_winding_transformer", "uk_12_max", [1, 30], "pk_12_max/sn_2") in validation_errors + ) + assert NotGreaterOrEqualError("three_winding_transformer", "uk_13_max", [29], "pk_13_max/sn_1") in validation_errors + assert NotGreaterOrEqualError("three_winding_transformer", "uk_13_max", [30], "pk_13_max/sn_3") in validation_errors + assert ( + NotGreaterOrEqualError("three_winding_transformer", "uk_23_max", [1, 29], "pk_23_max/sn_2") in validation_errors + ) + assert NotGreaterOrEqualError("three_winding_transformer", "uk_23_max", [30], "pk_23_max/sn_3") in validation_errors + assert NotBetweenError("three_winding_transformer", "uk_12_max", [1, 28], (0, 1)) in validation_errors + assert NotBetweenError("three_winding_transformer", "uk_13_max", [1, 28], (0, 1)) in validation_errors + assert NotBetweenError("three_winding_transformer", "uk_23_max", [1, 28], (0, 1)) in validation_errors + assert NotGreaterOrEqualError("three_winding_transformer", "pk_12_min", [1], 0) in validation_errors + assert NotGreaterOrEqualError("three_winding_transformer", "pk_13_min", [1], 0) in validation_errors + assert NotGreaterOrEqualError("three_winding_transformer", "pk_23_min", [1], 0) in validation_errors + assert NotGreaterOrEqualError("three_winding_transformer", "pk_12_max", [1], 0) in validation_errors + assert NotGreaterOrEqualError("three_winding_transformer", "pk_13_max", [1], 0) in validation_errors + assert NotGreaterOrEqualError("three_winding_transformer", "pk_23_max", [1], 0) in validation_errors + + def test_validate_input_data_asym_calculation(input_data): validation_errors = validate_input_data(input_data, symmetric=False) assert NotGreaterThanError("node", "u_rated", [1], 0) in validation_errors diff --git a/tests/unit/validation/test_validation_functions.py b/tests/unit/validation/test_validation_functions.py index 0a58969b44..1ff6ba87b9 100644 --- a/tests/unit/validation/test_validation_functions.py +++ b/tests/unit/validation/test_validation_functions.py @@ -138,6 +138,7 @@ def test_validate_required_values_sym_calculation(calculation_type, symmetric): line = initialize_array("input", "line", 1) link = initialize_array("input", "link", 1) transformer = initialize_array("input", "transformer", 1) + three_winding_transformer = initialize_array("input", "three_winding_transformer", 1) source = initialize_array("input", "source", 1) shunt = initialize_array("input", "shunt", 1) sym_load = initialize_array("input", "sym_load", 1) @@ -160,6 +161,7 @@ def test_validate_required_values_sym_calculation(calculation_type, symmetric): "line": line, "link": link, "transformer": transformer, + "three_winding_transformer": three_winding_transformer, "source": source, "shunt": shunt, "sym_load": sym_load, @@ -222,6 +224,38 @@ def test_validate_required_values_sym_calculation(calculation_type, symmetric): assert MissingValueError("transformer", "tap_max", [NaN]) in required_values_errors assert MissingValueError("transformer", "tap_size", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "id", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "node_1", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "node_2", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "node_3", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "status_1", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "status_2", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "status_3", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "u1", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "u2", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "u3", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "sn_1", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "sn_2", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "sn_3", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "uk_12", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "uk_13", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "uk_23", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "pk_12", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "pk_13", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "pk_23", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "i0", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "p0", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "winding_1", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "winding_2", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "winding_3", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "clock_12", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "clock_13", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "tap_side", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "tap_pos", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "tap_min", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "tap_max", [NaN]) in required_values_errors + assert MissingValueError("three_winding_transformer", "tap_size", [NaN]) in required_values_errors + assert MissingValueError("source", "id", [NaN]) in required_values_errors assert MissingValueError("source", "node", [NaN]) in required_values_errors assert MissingValueError("source", "status", [NaN]) in required_values_errors