diff --git a/code_generation/data/attribute_classes/input.json b/code_generation/data/attribute_classes/input.json index 7cf8b5c97..098bf92dc 100644 --- a/code_generation/data/attribute_classes/input.json +++ b/code_generation/data/attribute_classes/input.json @@ -127,6 +127,51 @@ } ] }, + { + "name": "AsymLineInput", + "base": "BranchInput", + "attributes": [ + { + "data_type": "double", + "names": [ + "r_aa", + "r_ba", + "r_bb", + "r_ca", + "r_cb", + "r_cc", + "r_na", + "r_nb", + "r_nc", + "r_nn", + "x_aa", + "x_ba", + "x_bb", + "x_ca", + "x_cb", + "x_cc", + "x_na", + "x_nb", + "x_nc", + "x_nn", + "c_aa", + "c_ba", + "c_bb", + "c_ca", + "c_cb", + "c_cc", + "c0", + "c1" + ], + "description": "Lower triangle matrix values for R, X and C matrices" + }, + { + "data_type": "double", + "names": "i_n", + "description": "rated current" + } + ] + }, { "name": "GenericBranchInput", "base": "BranchInput", diff --git a/code_generation/data/dataset_class_maps/dataset_definitions.json b/code_generation/data/dataset_class_maps/dataset_definitions.json index 24ef30c29..ea6b2e894 100644 --- a/code_generation/data/dataset_class_maps/dataset_definitions.json +++ b/code_generation/data/dataset_class_maps/dataset_definitions.json @@ -12,6 +12,10 @@ "names": ["line"], "class_name": "LineInput" }, + { + "names": ["asym_line"], + "class_name": "AsymLineInput" + }, { "names": ["link"], "class_name": "LinkInput" @@ -71,7 +75,7 @@ "class_name": "NodeOutput" }, { - "names": ["line", "link", "transformer", "generic_branch"], + "names": ["line", "link", "transformer", "generic_branch", "asym_line"], "class_name": "BranchOutput" }, { @@ -116,6 +120,10 @@ "names": ["line"], "class_name": "BranchUpdate" }, + { + "names": ["asym_line"], + "class_name": "BranchUpdate" + }, { "names": ["link"], "class_name": "BranchUpdate" @@ -171,7 +179,7 @@ "class_name": "NodeShortCircuitOutput" }, { - "names": ["line", "link", "transformer"], + "names": ["line", "link", "transformer", "asym_line"], "class_name": "BranchShortCircuitOutput" }, { diff --git a/docs/user_manual/components.md b/docs/user_manual/components.md index 10418ce73..38845ac25 100644 --- a/docs/user_manual/components.md +++ b/docs/user_manual/components.md @@ -276,6 +276,133 @@ $$ \end{eqnarray} $$ +### Asym Line + +* type name: `asym_line` + +`asym_line` is a {hoverxreftooltip}`user_manual/components:branch` with specified resistance and reactance per phase. A cable can be modelled as `line` or `asym_line`. An `asym_line` can only connect two nodes with the same rated voltage. If `i_n` is not provided, `loading` of line will be a `nan` value. The `asym_line` denotes a 3 or 4 phase line with phases `a`, `b`, `c` and optionally `n` for neutral. + +#### Input + +The provided values will be converted to a matrix representing the line's properties per phase in the following form: + +$$ + \begin{bmatrix} + \text{aa} & \text{ba} & \text{ca} & \text{na}\\ + \text{ba} & \text{bb} & \text{cb} & \text{nb}\\ + \text{ca} & \text{cb} & \text{cc} & \text{nc}\\ + \text{na} & \text{nb} & \text{nc} & \text{nn} + \end{bmatrix} +$$ + +This representation holds for all values `r_aa` ... `r_nn`, `x_aa` ... `x_nn` and `c_aa` ... `c_cc`. If the neutral values are not provided, the last row and column from the above matrix are omitted. + +| name | data type | unit | description | required | update | valid values | +| ------ | --------- | ---------- | --------------------------------- | ---------------------------- | :------: | :--------------------------------: | +| `r_aa` | `double` | ohm (Ω) | Series serial resistance aa | ✔ | ❌ | `> 0` | +| `r_ba` | `double` | ohm (Ω) | Series serial resistance ba | ✔ | ❌ | `> 0` | +| `r_bb` | `double` | ohm (Ω) | Series serial resistance bb | ✔ | ❌ | `> 0` | +| `r_ca` | `double` | ohm (Ω) | Series serial resistance ca | ✔ | ❌ | `> 0` | +| `r_cb` | `double` | ohm (Ω) | Series serial resistance cb | ✔ | ❌ | `> 0` | +| `r_cc` | `double` | ohm (Ω) | Series serial resistance cc | ✔ | ❌ | `> 0` | +| `r_na` | `double` | ohm (Ω) | Series serial resistance na | ✨ for a neutral phase | ❌ | `> 0` | +| `r_nb` | `double` | ohm (Ω) | Series serial resistance nb | ✨ for a neutral phase | ❌ | `> 0` | +| `r_nc` | `double` | ohm (Ω) | Series serial resistance nc | ✨ for a neutral phase | ❌ | `> 0` | +| `r_nn` | `double` | ohm (Ω) | Series serial resistance nn | ✨ for a neutral phase | ❌ | `> 0` | +| `x_aa` | `double` | ohm (Ω) | Series serial reactance aa | ✔ | ❌ | `> 0` | +| `x_ba` | `double` | ohm (Ω) | Series serial reactance ba | ✔ | ❌ | `> 0` | +| `x_bb` | `double` | ohm (Ω) | Series serial reactance bb | ✔ | ❌ | `> 0` | +| `x_ca` | `double` | ohm (Ω) | Series serial reactance ca | ✔ | ❌ | `> 0` | +| `x_cb` | `double` | ohm (Ω) | Series serial reactance cb | ✔ | ❌ | `> 0` | +| `x_cc` | `double` | ohm (Ω) | Series serial reactance cc | ✔ | ❌ | `> 0` | +| `x_na` | `double` | ohm (Ω) | Series serial reactance na | ✨ for a neutral phase | ❌ | `> 0` | +| `x_nb` | `double` | ohm (Ω) | Series serial reactance nb | ✨ for a neutral phase | ❌ | `> 0` | +| `x_nc` | `double` | ohm (Ω) | Series serial reactance nc | ✨ for a neutral phase | ❌ | `> 0` | +| `x_nn` | `double` | ohm (Ω) | Series serial reactance nn | ✨ for a neutral phase | ❌ | `> 0` | +| `c_aa` | `double` | farad (F) | Shunt nodal capacitance matrix aa | ✨ for a full c matrix | ❌ | `> 0` | +| `c_ba` | `double` | farad (F) | Shunt nodal capacitance matrix ba | ✨ for a full c matrix | ❌ | `> 0` | +| `c_bb` | `double` | farad (F) | Shunt nodal capacitance matrix bb | ✨ for a full c matrix | ❌ | `> 0` | +| `c_ca` | `double` | farad (F) | Shunt nodal capacitance matrix ca | ✨ for a full c matrix | ❌ | `> 0` | +| `c_cb` | `double` | farad (F) | Shunt nodal capacitance matrix cb | ✨ for a full c matrix | ❌ | `> 0` | +| `c_cc` | `double` | farad (F) | Shunt nodal capacitance matrix cc | ✨ for a full c matrix | ❌ | `> 0` | +| `c0` | `double` | farad (F) | zero-sequence shunt capacitance | ✨ without a c matrix | ❌ | `> 0` | +| `c1` | `double` | farad (F) | Series shunt capacitance | ✨ without a c matrix | ❌ | `> 0` | + +For the r and x matrices providing values for the neutral phase is optional. To clarify which input values are required, please consult the tables below: + +| r_aa ... r_cc | r_na | r_nb | r_nc | r_nn | result | Validation Error | +| --------------- | -------- | -------- | -------- | -------- | -------- | ------------------------- | +| ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | +| ✔ | ✔ | ✔ | ✔ | ❌ | ❌ | MultiFieldValidationError | +| ✔ | ✔ | ✔ | ✔ | ❌ | ❌ | MultiFieldValidationError | +| ✔ | ✔ | ✔ | ❌ | ❌ | ❌ | MultiFieldValidationError | +| ✔ | ✔ | ❌ | ❌ | ❌ | ❌ | MultiFieldValidationError | +| ✔ | ❌ | ❌ | ❌ | ❌ | ✔ | | +| ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | MultiFieldValidationError | + +| x_aa ... x_cc | x_na | x_nb | x_nc | x_nn | result | Validation Error | +| --------------- | -------- | -------- | -------- | -------- | -------- | ------------------------- | +| ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | +| ✔ | ✔ | ✔ | ✔ | ❌ | ❌ | MultiFieldValidationError | +| ✔ | ✔ | ✔ | ✔ | ❌ | ❌ | MultiFieldValidationError | +| ✔ | ✔ | ✔ | ❌ | ❌ | ❌ | MultiFieldValidationError | +| ✔ | ✔ | ❌ | ❌ | ❌ | ❌ | MultiFieldValidationError | +| ✔ | ❌ | ❌ | ❌ | ❌ | ✔ | | +| ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | MultiFieldValidationError | + +For the c-matrix values there are two options. Either provide all the required c-matrix values i.e. `c_aa` ... `c_cc` or provide `c0`, `c1`. Whenver both sets are supplied the powerflow calculations will use `c0`, `c1`. +The table below provides guidance in providing valid input. + +| c_aa ... c_cc | c0 | c1 | result | Validation Error | +| ------------- | -------- | -------- | -------- | ------------------------- | +| ✔ | ✔ | ✔ | ✔ | | +| ✔ | ✔ | ❌ | ✔ | | +| ✔ | ❌ | ❌ | ✔ | | +| ❌ | ✔ | ❌ | ❌ | MultiFieldValidationError | +| ❌ | ✔ | ✔ | ✔ | | + +#### Electric Model + +The cable properties are described using matrices where the $Z_{\text{series}}$ matrix is computed as: + +$$ + Z_{\text{series}} = R + \mathrm{j} * X +$$ + +Where $R$ and $X$ denote the resistance and reactance matrices build from the input respectively. + +Whenever the neutral phase is provided in the $Z_{\text{series}}$, the $Z_{\text{series}}$ matrix will first be reduced to a 3 phase matrix $Z_{\text{reduced}}$ with a kron reduction as follows: + +$$ + Z_{\text{aa}} = \begin{bmatrix} + Z_{\text{0,0}} & Z_{\text{1,0}} & Z_{\text{2,0}}\\ + Z_{\text{1,0}} & Z_{\text{1,1}} & Z_{\text{2,1}}\\ + Z_{\text{2,0}} & Z_{\text{2,1}} & Z_{\text{2,2}} + \end{bmatrix} +$$ +$$ + Z_{\text{ab}} = \begin{bmatrix} + Z_{\text{0,3}} \\ + Z_{\text{1,3}} \\ + Z_{\text{2,3}} + \end{bmatrix} +$$ +$$ + Z_{\text{ba}} = \begin{bmatrix} + Z_{\text{3,0}} \\ + Z_{\text{3,1}} \\ + Z_{\text{3,2}} + \end{bmatrix} +$$ +$$ + Z_{\text{bb}}^{-1} = \frac{1}{Z_{\text{3,3}}} +$$ +$$ + Z_{\text{reduced}} = Z_{\text{aa}} - Z_{\text{ba}} \otimes Z_{\text{ab}} \cdot Z_{\text{bb}}^{-1} +$$ + +Where $Z_{\text{i,j}}$ denotes the row and column of the $Z_{\text{series}}$ matrix. + ## Branch3 * type name: `branch3` diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/all_components.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/all_components.hpp index 617d48455..1c80bbcef 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/all_components.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/all_components.hpp @@ -9,6 +9,7 @@ #include "common/component_list.hpp" // component include #include "component/appliance.hpp" +#include "component/asym_line.hpp" #include "component/current_sensor.hpp" #include "component/fault.hpp" #include "component/generic_branch.hpp" @@ -28,8 +29,8 @@ namespace power_grid_model { using AllComponents = - ComponentList; template diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/input.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/input.hpp index 661579d1c..b776850eb 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/input.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/input.hpp @@ -98,6 +98,51 @@ struct LineInput { operator BranchInput const&() const { return reinterpret_cast(*this); } }; +struct AsymLineInput { + ID id{na_IntID}; // ID of the object + ID from_node{na_IntID}; // node IDs to which this branch is connected at both sides + ID to_node{na_IntID}; // node IDs to which this branch is connected at both sides + IntS from_status{na_IntS}; // whether the branch is connected at each side + IntS to_status{na_IntS}; // whether the branch is connected at each side + double r_aa{nan}; // Lower triangle matrix values for R, X and C matrices + double r_ba{nan}; // Lower triangle matrix values for R, X and C matrices + double r_bb{nan}; // Lower triangle matrix values for R, X and C matrices + double r_ca{nan}; // Lower triangle matrix values for R, X and C matrices + double r_cb{nan}; // Lower triangle matrix values for R, X and C matrices + double r_cc{nan}; // Lower triangle matrix values for R, X and C matrices + double r_na{nan}; // Lower triangle matrix values for R, X and C matrices + double r_nb{nan}; // Lower triangle matrix values for R, X and C matrices + double r_nc{nan}; // Lower triangle matrix values for R, X and C matrices + double r_nn{nan}; // Lower triangle matrix values for R, X and C matrices + double x_aa{nan}; // Lower triangle matrix values for R, X and C matrices + double x_ba{nan}; // Lower triangle matrix values for R, X and C matrices + double x_bb{nan}; // Lower triangle matrix values for R, X and C matrices + double x_ca{nan}; // Lower triangle matrix values for R, X and C matrices + double x_cb{nan}; // Lower triangle matrix values for R, X and C matrices + double x_cc{nan}; // Lower triangle matrix values for R, X and C matrices + double x_na{nan}; // Lower triangle matrix values for R, X and C matrices + double x_nb{nan}; // Lower triangle matrix values for R, X and C matrices + double x_nc{nan}; // Lower triangle matrix values for R, X and C matrices + double x_nn{nan}; // Lower triangle matrix values for R, X and C matrices + double c_aa{nan}; // Lower triangle matrix values for R, X and C matrices + double c_ba{nan}; // Lower triangle matrix values for R, X and C matrices + double c_bb{nan}; // Lower triangle matrix values for R, X and C matrices + double c_ca{nan}; // Lower triangle matrix values for R, X and C matrices + double c_cb{nan}; // Lower triangle matrix values for R, X and C matrices + double c_cc{nan}; // Lower triangle matrix values for R, X and C matrices + double c0{nan}; // Lower triangle matrix values for R, X and C matrices + double c1{nan}; // Lower triangle matrix values for R, X and C matrices + double i_n{nan}; // rated current + + // implicit conversions to BaseInput + operator BaseInput&() { return reinterpret_cast(*this); } + operator BaseInput const&() const { return reinterpret_cast(*this); } + + // implicit conversions to BranchInput + operator BranchInput&() { return reinterpret_cast(*this); } + operator BranchInput const&() const { return reinterpret_cast(*this); } +}; + struct GenericBranchInput { ID id{na_IntID}; // ID of the object ID from_node{na_IntID}; // node IDs to which this branch is connected at both sides diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/meta_gen/input.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/meta_gen/input.hpp index 27deffff7..84c2286f6 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/meta_gen/input.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/meta_gen/input.hpp @@ -111,6 +111,48 @@ struct get_attributes_list { }; }; +template<> +struct get_attributes_list { + static constexpr std::array value{ + // all attributes including base class + + meta_data_gen::get_meta_attribute<&AsymLineInput::id>(offsetof(AsymLineInput, id), "id"), + meta_data_gen::get_meta_attribute<&AsymLineInput::from_node>(offsetof(AsymLineInput, from_node), "from_node"), + meta_data_gen::get_meta_attribute<&AsymLineInput::to_node>(offsetof(AsymLineInput, to_node), "to_node"), + meta_data_gen::get_meta_attribute<&AsymLineInput::from_status>(offsetof(AsymLineInput, from_status), "from_status"), + meta_data_gen::get_meta_attribute<&AsymLineInput::to_status>(offsetof(AsymLineInput, to_status), "to_status"), + meta_data_gen::get_meta_attribute<&AsymLineInput::r_aa>(offsetof(AsymLineInput, r_aa), "r_aa"), + meta_data_gen::get_meta_attribute<&AsymLineInput::r_ba>(offsetof(AsymLineInput, r_ba), "r_ba"), + meta_data_gen::get_meta_attribute<&AsymLineInput::r_bb>(offsetof(AsymLineInput, r_bb), "r_bb"), + meta_data_gen::get_meta_attribute<&AsymLineInput::r_ca>(offsetof(AsymLineInput, r_ca), "r_ca"), + meta_data_gen::get_meta_attribute<&AsymLineInput::r_cb>(offsetof(AsymLineInput, r_cb), "r_cb"), + meta_data_gen::get_meta_attribute<&AsymLineInput::r_cc>(offsetof(AsymLineInput, r_cc), "r_cc"), + meta_data_gen::get_meta_attribute<&AsymLineInput::r_na>(offsetof(AsymLineInput, r_na), "r_na"), + meta_data_gen::get_meta_attribute<&AsymLineInput::r_nb>(offsetof(AsymLineInput, r_nb), "r_nb"), + meta_data_gen::get_meta_attribute<&AsymLineInput::r_nc>(offsetof(AsymLineInput, r_nc), "r_nc"), + meta_data_gen::get_meta_attribute<&AsymLineInput::r_nn>(offsetof(AsymLineInput, r_nn), "r_nn"), + meta_data_gen::get_meta_attribute<&AsymLineInput::x_aa>(offsetof(AsymLineInput, x_aa), "x_aa"), + meta_data_gen::get_meta_attribute<&AsymLineInput::x_ba>(offsetof(AsymLineInput, x_ba), "x_ba"), + meta_data_gen::get_meta_attribute<&AsymLineInput::x_bb>(offsetof(AsymLineInput, x_bb), "x_bb"), + meta_data_gen::get_meta_attribute<&AsymLineInput::x_ca>(offsetof(AsymLineInput, x_ca), "x_ca"), + meta_data_gen::get_meta_attribute<&AsymLineInput::x_cb>(offsetof(AsymLineInput, x_cb), "x_cb"), + meta_data_gen::get_meta_attribute<&AsymLineInput::x_cc>(offsetof(AsymLineInput, x_cc), "x_cc"), + meta_data_gen::get_meta_attribute<&AsymLineInput::x_na>(offsetof(AsymLineInput, x_na), "x_na"), + meta_data_gen::get_meta_attribute<&AsymLineInput::x_nb>(offsetof(AsymLineInput, x_nb), "x_nb"), + meta_data_gen::get_meta_attribute<&AsymLineInput::x_nc>(offsetof(AsymLineInput, x_nc), "x_nc"), + meta_data_gen::get_meta_attribute<&AsymLineInput::x_nn>(offsetof(AsymLineInput, x_nn), "x_nn"), + meta_data_gen::get_meta_attribute<&AsymLineInput::c_aa>(offsetof(AsymLineInput, c_aa), "c_aa"), + meta_data_gen::get_meta_attribute<&AsymLineInput::c_ba>(offsetof(AsymLineInput, c_ba), "c_ba"), + meta_data_gen::get_meta_attribute<&AsymLineInput::c_bb>(offsetof(AsymLineInput, c_bb), "c_bb"), + meta_data_gen::get_meta_attribute<&AsymLineInput::c_ca>(offsetof(AsymLineInput, c_ca), "c_ca"), + meta_data_gen::get_meta_attribute<&AsymLineInput::c_cb>(offsetof(AsymLineInput, c_cb), "c_cb"), + meta_data_gen::get_meta_attribute<&AsymLineInput::c_cc>(offsetof(AsymLineInput, c_cc), "c_cc"), + meta_data_gen::get_meta_attribute<&AsymLineInput::c0>(offsetof(AsymLineInput, c0), "c0"), + meta_data_gen::get_meta_attribute<&AsymLineInput::c1>(offsetof(AsymLineInput, c1), "c1"), + meta_data_gen::get_meta_attribute<&AsymLineInput::i_n>(offsetof(AsymLineInput, i_n), "i_n"), + }; +}; + template<> struct get_attributes_list { static constexpr std::array value{ diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/static_asserts/input.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/static_asserts/input.hpp index ee0052125..76913f717 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/static_asserts/input.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/auxiliary/static_asserts/input.hpp @@ -70,6 +70,25 @@ static_assert(offsetof(LineInput, to_node) == offsetof(BranchInput, to_node)); static_assert(offsetof(LineInput, from_status) == offsetof(BranchInput, from_status)); static_assert(offsetof(LineInput, to_status) == offsetof(BranchInput, to_status)); +// static asserts for AsymLineInput +static_assert(std::is_standard_layout_v); +// static asserts for conversion of AsymLineInput to BaseInput +static_assert(std::alignment_of_v >= std::alignment_of_v); +static_assert(std::same_as); +static_assert(offsetof(AsymLineInput, id) == offsetof(BaseInput, id)); +// static asserts for conversion of AsymLineInput to BranchInput +static_assert(std::alignment_of_v >= std::alignment_of_v); +static_assert(std::same_as); +static_assert(std::same_as); +static_assert(std::same_as); +static_assert(std::same_as); +static_assert(std::same_as); +static_assert(offsetof(AsymLineInput, id) == offsetof(BranchInput, id)); +static_assert(offsetof(AsymLineInput, from_node) == offsetof(BranchInput, from_node)); +static_assert(offsetof(AsymLineInput, to_node) == offsetof(BranchInput, to_node)); +static_assert(offsetof(AsymLineInput, from_status) == offsetof(BranchInput, from_status)); +static_assert(offsetof(AsymLineInput, to_status) == offsetof(BranchInput, to_status)); + // static asserts for GenericBranchInput static_assert(std::is_standard_layout_v); // static asserts for conversion of GenericBranchInput to BaseInput diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/common/matrix_utils.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/common/matrix_utils.hpp new file mode 100644 index 000000000..a80e517d4 --- /dev/null +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/common/matrix_utils.hpp @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: Contributors to the Power Grid Model project +// +// SPDX-License-Identifier: MPL-2.0 + +#pragma once + +#include "three_phase_tensor.hpp" + +namespace power_grid_model { + +inline DoubleComplex average_of_diagonal_of_matrix(const ComplexTensor& matrix) { + return (matrix(0, 0) + matrix(1, 1) + matrix(2, 2)) / 3.0; +} + +inline DoubleComplex average_of_off_diagonal_of_matrix(const ComplexTensor& matrix) { + return (matrix(0, 1) + matrix(1, 2) + matrix(1, 0) + matrix(1, 2) + matrix(2, 0) + matrix(2, 1)) / 6.0; +} + +} // namespace power_grid_model diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/common/three_phase_tensor.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/common/three_phase_tensor.hpp index 0d6ff9a79..7f6c65652 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/common/three_phase_tensor.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/common/three_phase_tensor.hpp @@ -22,6 +22,7 @@ namespace three_phase_tensor { template using Eigen3Vector = Eigen::Array; template using Eigen3Tensor = Eigen::Array; +template using Eigen4Tensor = Eigen::Array; template using Eigen3DiagonalTensor = Eigen::DiagonalMatrix; template class Vector : public Eigen3Vector { @@ -61,7 +62,13 @@ template class Tensor : public Eigen3Tensor { // additional constructors explicit Tensor(T const& x) { (*this) << x, 0.0, 0.0, 0.0, x, 0.0, 0.0, 0.0, x; } explicit Tensor(T const& s, T const& m) { (*this) << s, m, m, m, s, m, m, m, s; } - explicit Tensor(Vector const& v) { (*this) << v(0), 0.0, 0.0, 0.0, v(1), 0.0, 0.0, 0.0, v(2); } + explicit Tensor(T const& s1, T const& s2, T const& s3, T const& m12, T const& m13, T const& m23) { + (*this) << s1, m12, m13, m12, s2, m23, m13, m23, s3; + } + explicit Tensor(Vector const& v) { + assert(v.size() == 3); + (*this) << v(0), 0.0, 0.0, 0.0, v(1), 0.0, 0.0, 0.0, v(2); + } // eigen expression template Tensor(Eigen::ArrayBase const& other) : Eigen3Tensor{other} {} template Tensor& operator=(Eigen::ArrayBase const& other) { @@ -70,6 +77,28 @@ template class Tensor : public Eigen3Tensor { } }; +template class Tensor4 : public Eigen4Tensor { + public: + Tensor4() { (*this) = Eigen4Tensor::Zero(); } + // additional constructors + explicit Tensor4(T const& x) { (*this) << x, 0.0, 0.0, 0.0, 0.0, x, 0.0, 0.0, 0.0, 0.0, x, 0.0, 0.0, 0.0, 0.0, x; } + explicit Tensor4(T const& s, T const& m) { (*this) << s, m, m, m, m, s, m, m, m, m, s, m, m, m, m, s; } + explicit Tensor4(T const& s1, T const& s2, T const& s3, T const& s4, T const& m12, T const& m13, T const& m14, + T const& m23, T const& m24, T const& m34) { + (*this) << s1, m12, m13, m14, m12, s2, m23, m24, m13, m23, s3, m34, m14, m24, m34, s4; + } + explicit Tensor4(Vector const& v) { + assert(v.size() == 4); + (*this) << v(0), 0.0, 0.0, 0.0, 0.0, v(1), 0.0, 0.0, 0.0, 0.0, v(2), 0.0, 0.0, 0.0, 0.0, v(3); + } + // eigen expression + template Tensor4(Eigen::ArrayBase const& other) : Eigen4Tensor{other} {} + template Tensor4& operator=(Eigen::ArrayBase const& other) { + this->Eigen4Tensor::operator=(other); + return *this; + } +}; + template class DiagonalTensor : public Eigen3DiagonalTensor { public: DiagonalTensor() { (*this).setZero(); } @@ -92,6 +121,7 @@ template using RealTensor = std::conditional_t, double, three_phase_tensor::Tensor>; template using ComplexTensor = std::conditional_t, DoubleComplex, three_phase_tensor::Tensor>; +using ComplexTensor4 = three_phase_tensor::Tensor4; template using RealDiagonalTensor = std::conditional_t, double, three_phase_tensor::DiagonalTensor>; diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/component/asym_line.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/component/asym_line.hpp new file mode 100644 index 000000000..d087fea3d --- /dev/null +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/component/asym_line.hpp @@ -0,0 +1,128 @@ +// SPDX-FileCopyrightText: Contributors to the Power Grid Model project +// +// SPDX-License-Identifier: MPL-2.0 + +#pragma once + +#include + +#include "branch.hpp" +#include + +#include "../auxiliary/input.hpp" +#include "../auxiliary/output.hpp" +#include "../auxiliary/update.hpp" +#include "../calculation_parameters.hpp" +#include "../common/common.hpp" +#include "../common/matrix_utils.hpp" +#include "../common/three_phase_tensor.hpp" +#include "line_utils.hpp" + +namespace power_grid_model { + +class AsymLine : public Branch { + public: + using InputType = AsymLineInput; + using UpdateType = BranchUpdate; + static constexpr char const* name = "asym_line"; + + explicit AsymLine(AsymLineInput const& asym_line_input, double system_frequency, double u1, double u2) + : Branch{asym_line_input}, i_n_{asym_line_input.i_n}, base_i_{base_power_3p / u1 / sqrt3} { + if (cabs(u1 - u2) > numerical_tolerance) { + throw ConflictVoltage{id(), from_node(), to_node(), u1, u2}; + } + + ComplexTensor c_matrix = compute_c_matrix_from_input(asym_line_input); + ComplexTensor z_series = compute_z_series_from_input(asym_line_input); + + double const base_y = base_i_ / (u1 / sqrt3); + + y_series_abc_ = 1 / base_y * inv(z_series); + y_shunt_abc_ = 1 / base_y * (2.0i * pi * system_frequency * c_matrix); + } + + // override getter + constexpr double base_i_from() const override { return base_i_; } + constexpr double base_i_to() const override { return base_i_; } + constexpr double loading(double /* max_s */, double max_i) const override { return max_i / i_n_; }; + constexpr double phase_shift() const override { return 0.0; } + constexpr bool is_param_mutable() const override { return false; } + + private: + double i_n_{}; + double base_i_{}; + ComplexTensor y_series_abc_{}; + ComplexTensor y_shunt_abc_{}; + + ComplexTensor compute_z_series_from_input(const power_grid_model::AsymLineInput& asym_line_input) { + ComplexTensor z_series_abc; + if (is_nan(asym_line_input.r_na) && is_nan(asym_line_input.x_na)) { + ComplexTensor r_matrix = + ComplexTensor(asym_line_input.r_aa, asym_line_input.r_bb, asym_line_input.r_cc, + asym_line_input.r_ba, asym_line_input.r_ca, asym_line_input.r_cb); + ComplexTensor x_matrix = + ComplexTensor(asym_line_input.x_aa, asym_line_input.x_bb, asym_line_input.x_cc, + asym_line_input.x_ba, asym_line_input.x_ca, asym_line_input.x_cb); + z_series_abc = r_matrix + 1.0i * x_matrix; + } else { + ComplexTensor4 r_matrix = + ComplexTensor4(asym_line_input.r_aa, asym_line_input.r_bb, asym_line_input.r_cc, asym_line_input.r_nn, + asym_line_input.r_ba, asym_line_input.r_ca, asym_line_input.r_na, asym_line_input.r_cb, + asym_line_input.r_nb, asym_line_input.r_nc); + ComplexTensor4 x_matrix = + ComplexTensor4(asym_line_input.x_aa, asym_line_input.x_bb, asym_line_input.x_cc, asym_line_input.x_nn, + asym_line_input.x_ba, asym_line_input.x_ca, asym_line_input.x_na, asym_line_input.x_cb, + asym_line_input.x_nb, asym_line_input.x_nc); + ComplexTensor4 z = r_matrix + 1.0i * x_matrix; + z_series_abc = kron_reduction(z); + } + return z_series_abc; + } + + ComplexTensor compute_c_matrix_from_input(const power_grid_model::AsymLineInput& asym_line_input) { + ComplexTensor c_matrix; + if (!is_nan(asym_line_input.c0) && !is_nan(asym_line_input.c1)) { + c_matrix = ComplexTensor{(2.0 * asym_line_input.c1 + asym_line_input.c0) / 3.0, + (asym_line_input.c0 - asym_line_input.c1) / 3.0}; + } else { + c_matrix = ComplexTensor(asym_line_input.c_aa, asym_line_input.c_bb, asym_line_input.c_cc, + asym_line_input.c_ba, asym_line_input.c_ca, asym_line_input.c_cb); + } + return c_matrix; + } + + BranchCalcParam sym_calc_param() const override final { + DoubleComplex y1_series = + average_of_diagonal_of_matrix(y_series_abc_) - average_of_off_diagonal_of_matrix(y_series_abc_); + DoubleComplex y1_shunt = + average_of_diagonal_of_matrix(y_shunt_abc_) - average_of_off_diagonal_of_matrix(y_shunt_abc_); + return calc_param_y_sym(y1_series, y1_shunt, 1.0); + } + + BranchCalcParam asym_calc_param() const override final { + BranchCalcParam param{}; + // not both connected + if (!branch_status()) { + // single connected + if (from_status() || to_status()) { + // branch_shunt = 0.5 * y_shunt + 1.0 / (1.0 / y_series + 2.0 / y_shunt); + ComplexTensor branch_shunt = ComplexTensor(); + if ((cabs(y_shunt_abc_) >= numerical_tolerance).all()) { + branch_shunt = 0.5 * y_shunt_abc_ + inv(inv(y_series_abc_) + 2.0 * inv(y_shunt_abc_)); + } + // from or to connected + param.yff() = from_status() ? branch_shunt : ComplexTensor(); + param.ytt() = to_status() ? branch_shunt : ComplexTensor(); + } + } + // both connected + else { + param.ytt() = y_series_abc_ + 0.5 * y_shunt_abc_; + param.yff() = param.ytt(); + param.yft() = -y_series_abc_; + param.ytf() = -y_series_abc_; + } + return param; + } +}; +} // namespace power_grid_model diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/component/line_utils.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/component/line_utils.hpp new file mode 100644 index 000000000..d2c54e01c --- /dev/null +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/component/line_utils.hpp @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: Contributors to the Power Grid Model project +// +// SPDX-License-Identifier: MPL-2.0 + +#pragma once + +#include "../common/three_phase_tensor.hpp" + +namespace power_grid_model { + +inline ComplexTensor kron_reduction(const ComplexTensor4& matrix_to_reduce) { + ComplexTensor4 Y = matrix_to_reduce; + ComplexTensor Y_aa = + ComplexTensor(Y(0, 0), Y(1, 1), Y(2, 2), Y(1, 0), Y(2, 0), Y(2, 1)); + ComplexValue Y_ab(Y(0, 3), Y(1, 3), Y(2, 3)); + ComplexValue Y_ba(Y(3, 0), Y(3, 1), Y(3, 2)); + DoubleComplex Y_bb_inv = 1.0 / Y(3, 3); + return Y_aa - vector_outer_product(Y_ba, Y_ab) * Y_bb_inv; +} + +} // namespace power_grid_model diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/input.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/input.hpp index 40e52dec1..9758c1fbc 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/input.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/input.hpp @@ -41,7 +41,7 @@ inline void add_component(MainModelState& state, ForwardIter double const u1 = get_component(state, input.from_node).u_rated(); double const u2 = get_component(state, input.to_node).u_rated(); // set system frequency for line - if constexpr (std::same_as) { + if constexpr (std::same_as || std::same_as) { emplace_component(state, id, input, system_frequency, u1, u2); } else { emplace_component(state, id, input, u1, u2); diff --git a/power_grid_model_c/power_grid_model_c/include/power_grid_model_c/dataset_definitions.h b/power_grid_model_c/power_grid_model_c/include/power_grid_model_c/dataset_definitions.h index 63f0f5882..ce739b0ee 100644 --- a/power_grid_model_c/power_grid_model_c/include/power_grid_model_c/dataset_definitions.h +++ b/power_grid_model_c/power_grid_model_c/include/power_grid_model_c/dataset_definitions.h @@ -47,6 +47,43 @@ PGM_API extern PGM_MetaAttribute const* const PGM_def_input_line_x0; PGM_API extern PGM_MetaAttribute const* const PGM_def_input_line_c0; PGM_API extern PGM_MetaAttribute const* const PGM_def_input_line_tan0; PGM_API extern PGM_MetaAttribute const* const PGM_def_input_line_i_n; +// component asym_line +PGM_API extern PGM_MetaComponent const* const PGM_def_input_asym_line; +// attributes of input asym_line +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_id; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_from_node; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_to_node; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_from_status; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_to_status; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_r_aa; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_r_ba; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_r_bb; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_r_ca; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_r_cb; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_r_cc; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_r_na; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_r_nb; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_r_nc; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_r_nn; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_x_aa; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_x_ba; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_x_bb; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_x_ca; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_x_cb; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_x_cc; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_x_na; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_x_nb; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_x_nc; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_x_nn; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_c_aa; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_c_ba; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_c_bb; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_c_ca; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_c_cb; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_c_cc; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_c0; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_c1; +PGM_API extern PGM_MetaAttribute const* const PGM_def_input_asym_line_i_n; // component link PGM_API extern PGM_MetaComponent const* const PGM_def_input_link; // attributes of input link @@ -362,6 +399,20 @@ PGM_API extern PGM_MetaAttribute const* const PGM_def_sym_output_generic_branch_ PGM_API extern PGM_MetaAttribute const* const PGM_def_sym_output_generic_branch_q_to; PGM_API extern PGM_MetaAttribute const* const PGM_def_sym_output_generic_branch_i_to; PGM_API extern PGM_MetaAttribute const* const PGM_def_sym_output_generic_branch_s_to; +// component asym_line +PGM_API extern PGM_MetaComponent const* const PGM_def_sym_output_asym_line; +// attributes of sym_output asym_line +PGM_API extern PGM_MetaAttribute const* const PGM_def_sym_output_asym_line_id; +PGM_API extern PGM_MetaAttribute const* const PGM_def_sym_output_asym_line_energized; +PGM_API extern PGM_MetaAttribute const* const PGM_def_sym_output_asym_line_loading; +PGM_API extern PGM_MetaAttribute const* const PGM_def_sym_output_asym_line_p_from; +PGM_API extern PGM_MetaAttribute const* const PGM_def_sym_output_asym_line_q_from; +PGM_API extern PGM_MetaAttribute const* const PGM_def_sym_output_asym_line_i_from; +PGM_API extern PGM_MetaAttribute const* const PGM_def_sym_output_asym_line_s_from; +PGM_API extern PGM_MetaAttribute const* const PGM_def_sym_output_asym_line_p_to; +PGM_API extern PGM_MetaAttribute const* const PGM_def_sym_output_asym_line_q_to; +PGM_API extern PGM_MetaAttribute const* const PGM_def_sym_output_asym_line_i_to; +PGM_API extern PGM_MetaAttribute const* const PGM_def_sym_output_asym_line_s_to; // component transformer_tap_regulator PGM_API extern PGM_MetaComponent const* const PGM_def_sym_output_transformer_tap_regulator; // attributes of sym_output transformer_tap_regulator @@ -562,6 +613,20 @@ PGM_API extern PGM_MetaAttribute const* const PGM_def_asym_output_generic_branch PGM_API extern PGM_MetaAttribute const* const PGM_def_asym_output_generic_branch_q_to; PGM_API extern PGM_MetaAttribute const* const PGM_def_asym_output_generic_branch_i_to; PGM_API extern PGM_MetaAttribute const* const PGM_def_asym_output_generic_branch_s_to; +// component asym_line +PGM_API extern PGM_MetaComponent const* const PGM_def_asym_output_asym_line; +// attributes of asym_output asym_line +PGM_API extern PGM_MetaAttribute const* const PGM_def_asym_output_asym_line_id; +PGM_API extern PGM_MetaAttribute const* const PGM_def_asym_output_asym_line_energized; +PGM_API extern PGM_MetaAttribute const* const PGM_def_asym_output_asym_line_loading; +PGM_API extern PGM_MetaAttribute const* const PGM_def_asym_output_asym_line_p_from; +PGM_API extern PGM_MetaAttribute const* const PGM_def_asym_output_asym_line_q_from; +PGM_API extern PGM_MetaAttribute const* const PGM_def_asym_output_asym_line_i_from; +PGM_API extern PGM_MetaAttribute const* const PGM_def_asym_output_asym_line_s_from; +PGM_API extern PGM_MetaAttribute const* const PGM_def_asym_output_asym_line_p_to; +PGM_API extern PGM_MetaAttribute const* const PGM_def_asym_output_asym_line_q_to; +PGM_API extern PGM_MetaAttribute const* const PGM_def_asym_output_asym_line_i_to; +PGM_API extern PGM_MetaAttribute const* const PGM_def_asym_output_asym_line_s_to; // component transformer_tap_regulator PGM_API extern PGM_MetaComponent const* const PGM_def_asym_output_transformer_tap_regulator; // attributes of asym_output transformer_tap_regulator @@ -706,6 +771,12 @@ PGM_API extern PGM_MetaComponent const* const PGM_def_update_line; PGM_API extern PGM_MetaAttribute const* const PGM_def_update_line_id; PGM_API extern PGM_MetaAttribute const* const PGM_def_update_line_from_status; PGM_API extern PGM_MetaAttribute const* const PGM_def_update_line_to_status; +// component asym_line +PGM_API extern PGM_MetaComponent const* const PGM_def_update_asym_line; +// attributes of update asym_line +PGM_API extern PGM_MetaAttribute const* const PGM_def_update_asym_line_id; +PGM_API extern PGM_MetaAttribute const* const PGM_def_update_asym_line_from_status; +PGM_API extern PGM_MetaAttribute const* const PGM_def_update_asym_line_to_status; // component link PGM_API extern PGM_MetaComponent const* const PGM_def_update_link; // attributes of update link @@ -872,6 +943,15 @@ PGM_API extern PGM_MetaAttribute const* const PGM_def_sc_output_transformer_i_fr PGM_API extern PGM_MetaAttribute const* const PGM_def_sc_output_transformer_i_from_angle; PGM_API extern PGM_MetaAttribute const* const PGM_def_sc_output_transformer_i_to; PGM_API extern PGM_MetaAttribute const* const PGM_def_sc_output_transformer_i_to_angle; +// component asym_line +PGM_API extern PGM_MetaComponent const* const PGM_def_sc_output_asym_line; +// attributes of sc_output asym_line +PGM_API extern PGM_MetaAttribute const* const PGM_def_sc_output_asym_line_id; +PGM_API extern PGM_MetaAttribute const* const PGM_def_sc_output_asym_line_energized; +PGM_API extern PGM_MetaAttribute const* const PGM_def_sc_output_asym_line_i_from; +PGM_API extern PGM_MetaAttribute const* const PGM_def_sc_output_asym_line_i_from_angle; +PGM_API extern PGM_MetaAttribute const* const PGM_def_sc_output_asym_line_i_to; +PGM_API extern PGM_MetaAttribute const* const PGM_def_sc_output_asym_line_i_to_angle; // component three_winding_transformer PGM_API extern PGM_MetaComponent const* const PGM_def_sc_output_three_winding_transformer; // attributes of sc_output three_winding_transformer diff --git a/power_grid_model_c/power_grid_model_c/src/dataset_definitions.cpp b/power_grid_model_c/power_grid_model_c/src/dataset_definitions.cpp index ceec394e6..0cd49e0a8 100644 --- a/power_grid_model_c/power_grid_model_c/src/dataset_definitions.cpp +++ b/power_grid_model_c/power_grid_model_c/src/dataset_definitions.cpp @@ -36,6 +36,43 @@ PGM_MetaAttribute const* const PGM_def_input_line_x0 = PGM_meta_get_attribute_by PGM_MetaAttribute const* const PGM_def_input_line_c0 = PGM_meta_get_attribute_by_name(nullptr, "input", "line", "c0"); PGM_MetaAttribute const* const PGM_def_input_line_tan0 = PGM_meta_get_attribute_by_name(nullptr, "input", "line", "tan0"); PGM_MetaAttribute const* const PGM_def_input_line_i_n = PGM_meta_get_attribute_by_name(nullptr, "input", "line", "i_n"); +// component asym_line +PGM_MetaComponent const* const PGM_def_input_asym_line = PGM_meta_get_component_by_name(nullptr, "input", "asym_line"); +// attributes of input asym_line +PGM_MetaAttribute const* const PGM_def_input_asym_line_id = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "id"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_from_node = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "from_node"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_to_node = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "to_node"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_from_status = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "from_status"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_to_status = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "to_status"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_r_aa = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "r_aa"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_r_ba = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "r_ba"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_r_bb = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "r_bb"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_r_ca = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "r_ca"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_r_cb = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "r_cb"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_r_cc = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "r_cc"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_r_na = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "r_na"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_r_nb = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "r_nb"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_r_nc = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "r_nc"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_r_nn = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "r_nn"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_x_aa = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "x_aa"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_x_ba = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "x_ba"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_x_bb = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "x_bb"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_x_ca = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "x_ca"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_x_cb = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "x_cb"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_x_cc = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "x_cc"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_x_na = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "x_na"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_x_nb = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "x_nb"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_x_nc = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "x_nc"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_x_nn = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "x_nn"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_c_aa = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "c_aa"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_c_ba = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "c_ba"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_c_bb = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "c_bb"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_c_ca = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "c_ca"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_c_cb = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "c_cb"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_c_cc = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "c_cc"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_c0 = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "c0"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_c1 = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "c1"); +PGM_MetaAttribute const* const PGM_def_input_asym_line_i_n = PGM_meta_get_attribute_by_name(nullptr, "input", "asym_line", "i_n"); // component link PGM_MetaComponent const* const PGM_def_input_link = PGM_meta_get_component_by_name(nullptr, "input", "link"); // attributes of input link @@ -351,6 +388,20 @@ PGM_MetaAttribute const* const PGM_def_sym_output_generic_branch_p_to = PGM_meta PGM_MetaAttribute const* const PGM_def_sym_output_generic_branch_q_to = PGM_meta_get_attribute_by_name(nullptr, "sym_output", "generic_branch", "q_to"); PGM_MetaAttribute const* const PGM_def_sym_output_generic_branch_i_to = PGM_meta_get_attribute_by_name(nullptr, "sym_output", "generic_branch", "i_to"); PGM_MetaAttribute const* const PGM_def_sym_output_generic_branch_s_to = PGM_meta_get_attribute_by_name(nullptr, "sym_output", "generic_branch", "s_to"); +// component asym_line +PGM_MetaComponent const* const PGM_def_sym_output_asym_line = PGM_meta_get_component_by_name(nullptr, "sym_output", "asym_line"); +// attributes of sym_output asym_line +PGM_MetaAttribute const* const PGM_def_sym_output_asym_line_id = PGM_meta_get_attribute_by_name(nullptr, "sym_output", "asym_line", "id"); +PGM_MetaAttribute const* const PGM_def_sym_output_asym_line_energized = PGM_meta_get_attribute_by_name(nullptr, "sym_output", "asym_line", "energized"); +PGM_MetaAttribute const* const PGM_def_sym_output_asym_line_loading = PGM_meta_get_attribute_by_name(nullptr, "sym_output", "asym_line", "loading"); +PGM_MetaAttribute const* const PGM_def_sym_output_asym_line_p_from = PGM_meta_get_attribute_by_name(nullptr, "sym_output", "asym_line", "p_from"); +PGM_MetaAttribute const* const PGM_def_sym_output_asym_line_q_from = PGM_meta_get_attribute_by_name(nullptr, "sym_output", "asym_line", "q_from"); +PGM_MetaAttribute const* const PGM_def_sym_output_asym_line_i_from = PGM_meta_get_attribute_by_name(nullptr, "sym_output", "asym_line", "i_from"); +PGM_MetaAttribute const* const PGM_def_sym_output_asym_line_s_from = PGM_meta_get_attribute_by_name(nullptr, "sym_output", "asym_line", "s_from"); +PGM_MetaAttribute const* const PGM_def_sym_output_asym_line_p_to = PGM_meta_get_attribute_by_name(nullptr, "sym_output", "asym_line", "p_to"); +PGM_MetaAttribute const* const PGM_def_sym_output_asym_line_q_to = PGM_meta_get_attribute_by_name(nullptr, "sym_output", "asym_line", "q_to"); +PGM_MetaAttribute const* const PGM_def_sym_output_asym_line_i_to = PGM_meta_get_attribute_by_name(nullptr, "sym_output", "asym_line", "i_to"); +PGM_MetaAttribute const* const PGM_def_sym_output_asym_line_s_to = PGM_meta_get_attribute_by_name(nullptr, "sym_output", "asym_line", "s_to"); // component transformer_tap_regulator PGM_MetaComponent const* const PGM_def_sym_output_transformer_tap_regulator = PGM_meta_get_component_by_name(nullptr, "sym_output", "transformer_tap_regulator"); // attributes of sym_output transformer_tap_regulator @@ -551,6 +602,20 @@ PGM_MetaAttribute const* const PGM_def_asym_output_generic_branch_p_to = PGM_met PGM_MetaAttribute const* const PGM_def_asym_output_generic_branch_q_to = PGM_meta_get_attribute_by_name(nullptr, "asym_output", "generic_branch", "q_to"); PGM_MetaAttribute const* const PGM_def_asym_output_generic_branch_i_to = PGM_meta_get_attribute_by_name(nullptr, "asym_output", "generic_branch", "i_to"); PGM_MetaAttribute const* const PGM_def_asym_output_generic_branch_s_to = PGM_meta_get_attribute_by_name(nullptr, "asym_output", "generic_branch", "s_to"); +// component asym_line +PGM_MetaComponent const* const PGM_def_asym_output_asym_line = PGM_meta_get_component_by_name(nullptr, "asym_output", "asym_line"); +// attributes of asym_output asym_line +PGM_MetaAttribute const* const PGM_def_asym_output_asym_line_id = PGM_meta_get_attribute_by_name(nullptr, "asym_output", "asym_line", "id"); +PGM_MetaAttribute const* const PGM_def_asym_output_asym_line_energized = PGM_meta_get_attribute_by_name(nullptr, "asym_output", "asym_line", "energized"); +PGM_MetaAttribute const* const PGM_def_asym_output_asym_line_loading = PGM_meta_get_attribute_by_name(nullptr, "asym_output", "asym_line", "loading"); +PGM_MetaAttribute const* const PGM_def_asym_output_asym_line_p_from = PGM_meta_get_attribute_by_name(nullptr, "asym_output", "asym_line", "p_from"); +PGM_MetaAttribute const* const PGM_def_asym_output_asym_line_q_from = PGM_meta_get_attribute_by_name(nullptr, "asym_output", "asym_line", "q_from"); +PGM_MetaAttribute const* const PGM_def_asym_output_asym_line_i_from = PGM_meta_get_attribute_by_name(nullptr, "asym_output", "asym_line", "i_from"); +PGM_MetaAttribute const* const PGM_def_asym_output_asym_line_s_from = PGM_meta_get_attribute_by_name(nullptr, "asym_output", "asym_line", "s_from"); +PGM_MetaAttribute const* const PGM_def_asym_output_asym_line_p_to = PGM_meta_get_attribute_by_name(nullptr, "asym_output", "asym_line", "p_to"); +PGM_MetaAttribute const* const PGM_def_asym_output_asym_line_q_to = PGM_meta_get_attribute_by_name(nullptr, "asym_output", "asym_line", "q_to"); +PGM_MetaAttribute const* const PGM_def_asym_output_asym_line_i_to = PGM_meta_get_attribute_by_name(nullptr, "asym_output", "asym_line", "i_to"); +PGM_MetaAttribute const* const PGM_def_asym_output_asym_line_s_to = PGM_meta_get_attribute_by_name(nullptr, "asym_output", "asym_line", "s_to"); // component transformer_tap_regulator PGM_MetaComponent const* const PGM_def_asym_output_transformer_tap_regulator = PGM_meta_get_component_by_name(nullptr, "asym_output", "transformer_tap_regulator"); // attributes of asym_output transformer_tap_regulator @@ -695,6 +760,12 @@ PGM_MetaComponent const* const PGM_def_update_line = PGM_meta_get_component_by_n PGM_MetaAttribute const* const PGM_def_update_line_id = PGM_meta_get_attribute_by_name(nullptr, "update", "line", "id"); PGM_MetaAttribute const* const PGM_def_update_line_from_status = PGM_meta_get_attribute_by_name(nullptr, "update", "line", "from_status"); PGM_MetaAttribute const* const PGM_def_update_line_to_status = PGM_meta_get_attribute_by_name(nullptr, "update", "line", "to_status"); +// component asym_line +PGM_MetaComponent const* const PGM_def_update_asym_line = PGM_meta_get_component_by_name(nullptr, "update", "asym_line"); +// attributes of update asym_line +PGM_MetaAttribute const* const PGM_def_update_asym_line_id = PGM_meta_get_attribute_by_name(nullptr, "update", "asym_line", "id"); +PGM_MetaAttribute const* const PGM_def_update_asym_line_from_status = PGM_meta_get_attribute_by_name(nullptr, "update", "asym_line", "from_status"); +PGM_MetaAttribute const* const PGM_def_update_asym_line_to_status = PGM_meta_get_attribute_by_name(nullptr, "update", "asym_line", "to_status"); // component link PGM_MetaComponent const* const PGM_def_update_link = PGM_meta_get_component_by_name(nullptr, "update", "link"); // attributes of update link @@ -861,6 +932,15 @@ PGM_MetaAttribute const* const PGM_def_sc_output_transformer_i_from = PGM_meta_g PGM_MetaAttribute const* const PGM_def_sc_output_transformer_i_from_angle = PGM_meta_get_attribute_by_name(nullptr, "sc_output", "transformer", "i_from_angle"); PGM_MetaAttribute const* const PGM_def_sc_output_transformer_i_to = PGM_meta_get_attribute_by_name(nullptr, "sc_output", "transformer", "i_to"); PGM_MetaAttribute const* const PGM_def_sc_output_transformer_i_to_angle = PGM_meta_get_attribute_by_name(nullptr, "sc_output", "transformer", "i_to_angle"); +// component asym_line +PGM_MetaComponent const* const PGM_def_sc_output_asym_line = PGM_meta_get_component_by_name(nullptr, "sc_output", "asym_line"); +// attributes of sc_output asym_line +PGM_MetaAttribute const* const PGM_def_sc_output_asym_line_id = PGM_meta_get_attribute_by_name(nullptr, "sc_output", "asym_line", "id"); +PGM_MetaAttribute const* const PGM_def_sc_output_asym_line_energized = PGM_meta_get_attribute_by_name(nullptr, "sc_output", "asym_line", "energized"); +PGM_MetaAttribute const* const PGM_def_sc_output_asym_line_i_from = PGM_meta_get_attribute_by_name(nullptr, "sc_output", "asym_line", "i_from"); +PGM_MetaAttribute const* const PGM_def_sc_output_asym_line_i_from_angle = PGM_meta_get_attribute_by_name(nullptr, "sc_output", "asym_line", "i_from_angle"); +PGM_MetaAttribute const* const PGM_def_sc_output_asym_line_i_to = PGM_meta_get_attribute_by_name(nullptr, "sc_output", "asym_line", "i_to"); +PGM_MetaAttribute const* const PGM_def_sc_output_asym_line_i_to_angle = PGM_meta_get_attribute_by_name(nullptr, "sc_output", "asym_line", "i_to_angle"); // component three_winding_transformer PGM_MetaComponent const* const PGM_def_sc_output_three_winding_transformer = PGM_meta_get_component_by_name(nullptr, "sc_output", "three_winding_transformer"); // attributes of sc_output three_winding_transformer diff --git a/src/power_grid_model/_core/dataset_definitions.py b/src/power_grid_model/_core/dataset_definitions.py index 4a0ea9ba5..5edff5f86 100644 --- a/src/power_grid_model/_core/dataset_definitions.py +++ b/src/power_grid_model/_core/dataset_definitions.py @@ -56,6 +56,7 @@ class ComponentType(str, Enum, metaclass=_MetaEnum): node = "node" line = "line" + asym_line = "asym_line" link = "link" generic_branch = "generic_branch" transformer = "transformer" diff --git a/src/power_grid_model/validation/_rules.py b/src/power_grid_model/validation/_rules.py index 16826cb76..ec8b58778 100644 --- a/src/power_grid_model/validation/_rules.py +++ b/src/power_grid_model/validation/_rules.py @@ -53,6 +53,7 @@ InvalidIdError, MissingValueError, MultiComponentNotUniqueError, + MultiFieldValidationError, NotBetweenError, NotBetweenOrAtError, NotBooleanError, @@ -746,6 +747,100 @@ def all_finite(data: SingleDataset, exceptions: dict[ComponentType, list[str]] | return errors +def no_strict_subset_missing(data: SingleDataset, fields: list[str], component_type: ComponentType): + """ + Helper function that generates multi field validation errors if a subset of the supplied fields is missing. + If for an instance of component type all fields are missing or all fields are not missing then, + no error is returned for that instance. + In any other case an error for that id is returned. + + Args: + data: SingleDataset, pgm data + fields: List of fields + component_type: component type to check + """ + errors = [] + if component_type in data: + component_data = data[component_type] + instances_with_nan_data = np.full_like([], False, shape=(len(component_data),), dtype=bool) + instances_with_non_nan_data = np.full_like([], False, shape=(len(component_data),), dtype=bool) + for field in fields: + nan_value = _nan_type(component_type, field) + asym_axes = tuple(range(component_data.ndim, component_data[field].ndim)) + instances_with_nan_data = np.logical_or( + instances_with_nan_data, + np.any( + ( + np.isnan(component_data[field]) + if np.any(np.isnan(nan_value)) + else np.equal(component_data[field], nan_value) + ), + axis=asym_axes, + ), + ) + instances_with_non_nan_data = np.logical_or( + instances_with_non_nan_data, + np.any( + ( + np.logical_not(np.isnan(component_data[field])) + if np.any(np.isnan(nan_value)) + else np.logical_not(np.equal(component_data[field], nan_value)) + ), + axis=asym_axes, + ), + ) + + instances_with_invalid_data = np.logical_and(instances_with_nan_data, instances_with_non_nan_data) + + ids = component_data["id"][instances_with_invalid_data] + if len(ids) > 0: + errors.append(MultiFieldValidationError(component_type, fields, ids)) + + return errors + + +def not_all_missing(data: SingleDataset, fields: list[str], component_type: ComponentType): + """ + Helper function that generates a multi field validation error if: + all values specified by the fields parameters are missing. + + Args: + data: SingleDataset, pgm data + fields: List of fields + component_type: component type to check + """ + if len(fields) < 2: + raise ValueError( + "The fields parameter must contain at least 2 fields. Otherwise use the none_missing function." + ) + + errors = [] + if component_type in data: + component_data = data[component_type] + instances_with_all_nan_data = np.full_like([], True, shape=(len(component_data),), dtype=bool) + + for field in fields: + nan_value = _nan_type(component_type, field) + asym_axes = tuple(range(component_data.ndim, component_data[field].ndim)) + instances_with_all_nan_data = np.logical_and( + instances_with_all_nan_data, + np.any( + ( + np.isnan(component_data[field]) + if np.any(np.isnan(nan_value)) + else np.equal(component_data[field], nan_value) + ), + axis=asym_axes, + ), + ) + + ids = component_data["id"][instances_with_all_nan_data].flatten().tolist() + if len(ids) > 0: + errors.append(MultiFieldValidationError(component_type, fields, ids)) + + return errors + + def none_missing(data: SingleDataset, component: ComponentType, fields: str | list[str]) -> list[MissingValueError]: """ Check that for all records of a particular type of component, the values in the 'fields' columns are not NaN. diff --git a/src/power_grid_model/validation/_validation.py b/src/power_grid_model/validation/_validation.py index e8d17d951..ba8c5c29c 100644 --- a/src/power_grid_model/validation/_validation.py +++ b/src/power_grid_model/validation/_validation.py @@ -53,7 +53,9 @@ all_valid_fault_phases as _all_valid_fault_phases, all_valid_ids as _all_valid_ids, ids_valid_in_update_data_set as _ids_valid_in_update_data_set, + no_strict_subset_missing as _no_strict_subset_missing, none_missing as _none_missing, + not_all_missing as _not_all_missing, valid_p_q_sigma as _valid_p_q_sigma, ) from power_grid_model.validation.errors import ( @@ -295,6 +297,20 @@ def validate_required_values( required["branch"] = required["base"] + ["from_node", "to_node", "from_status", "to_status"] required["link"] = required["branch"].copy() required["line"] = required["branch"] + ["r1", "x1", "c1", "tan1"] + required["asym_line"] = required["branch"] + [ + "r_aa", + "r_ba", + "r_bb", + "r_ca", + "r_cb", + "r_cc", + "x_aa", + "x_ba", + "x_bb", + "x_ca", + "x_cb", + "x_cc", + ] required["transformer"] = required["branch"] + [ "u1", "u2", @@ -449,6 +465,7 @@ def validate_values(data: SingleDataset, calculation_type: CalculationType | Non component_validators = { "node": validate_node, "line": validate_line, + "asym_line": validate_asym_line, "link": lambda d: validate_branch(d, ComponentType.link), "generic_branch": validate_generic_branch, "transformer": validate_transformer, @@ -516,6 +533,29 @@ def validate_line(data: SingleDataset) -> list[ValidationError]: return errors +def validate_asym_line(data: SingleDataset) -> list[ValidationError]: + errors = validate_branch(data, ComponentType.asym_line) + errors += _all_greater_than_zero(data, ComponentType.asym_line, "i_n") + required_fields = ["r_aa", "r_ba", "r_bb", "r_ca", "r_cb", "r_cc", "x_aa", "x_ba", "x_bb", "x_ca", "x_cb", "x_cc"] + optional_r_matrix_fields = ["r_na", "r_nb", "r_nc", "r_nn"] + optional_x_matrix_fields = ["x_na", "x_nb", "x_nc", "x_nn"] + required_c_matrix_fields = ["c_aa", "c_ba", "c_bb", "c_ca", "c_cb", "c_cc"] + c_fields = ["c0", "c1"] + for field in ( + required_fields + optional_r_matrix_fields + optional_x_matrix_fields + required_c_matrix_fields + c_fields + ): + errors += _all_greater_than_zero(data, ComponentType.asym_line, field) + + errors += _no_strict_subset_missing( + data, optional_r_matrix_fields + optional_x_matrix_fields, ComponentType.asym_line + ) + errors += _no_strict_subset_missing(data, required_c_matrix_fields, ComponentType.asym_line) + errors += _no_strict_subset_missing(data, c_fields, ComponentType.asym_line) + errors += _not_all_missing(data, required_c_matrix_fields + c_fields, ComponentType.asym_line) + + return errors + + def validate_generic_branch(data: SingleDataset) -> list[ValidationError]: errors = validate_branch(data, ComponentType.generic_branch) errors += _all_greater_than_zero(data, ComponentType.generic_branch, "k") diff --git a/tests/cpp_unit_tests/CMakeLists.txt b/tests/cpp_unit_tests/CMakeLists.txt index 1b5331f57..84c4f0f78 100644 --- a/tests/cpp_unit_tests/CMakeLists.txt +++ b/tests/cpp_unit_tests/CMakeLists.txt @@ -15,6 +15,7 @@ set(PROJECT_SOURCES "test_three_phase_tensor.cpp" "test_statistics.cpp" "test_node.cpp" + "test_asym_line.cpp" "test_line.cpp" "test_generic_branch.cpp" "test_link.cpp" diff --git a/tests/cpp_unit_tests/test_asym_line.cpp b/tests/cpp_unit_tests/test_asym_line.cpp new file mode 100644 index 000000000..ce8a62ff7 --- /dev/null +++ b/tests/cpp_unit_tests/test_asym_line.cpp @@ -0,0 +1,402 @@ +// SPDX-FileCopyrightText: Contributors to the Power Grid Model project +// +// SPDX-License-Identifier: MPL-2.0 + +#include + +#include + +namespace power_grid_model { + +using namespace std::complex_literals; + +void execute_subcases(const AsymLineInput& input, const ComplexTensor& y_series, + const ComplexTensor& c_matrix, const double base_i, const double base_y, + const double system_frequency, const double voltage_lvl) { + CAPTURE(input.id); + CAPTURE(y_series); + AsymLine asym_line{input, system_frequency, voltage_lvl, voltage_lvl}; + Branch& branch = asym_line; + + ComplexTensor const y_shunt = 1 / base_y * (2 * pi * system_frequency * c_matrix * 1.0i); + + DoubleComplex const y1_series = + (y_series(0, 0) + y_series(1, 1) + y_series(2, 2)) / 3.0 - + (y_series(0, 1) + y_series(1, 2) + y_series(1, 0) + y_series(1, 2) + y_series(2, 0) + y_series(2, 1)) / 6.0; + DoubleComplex const y1_shunt = + (y_shunt(0, 0) + y_shunt(1, 1) + y_shunt(2, 2)) / 3.0 - + (y_shunt(0, 1) + y_shunt(1, 2) + y_shunt(1, 0) + y_shunt(1, 2) + y_shunt(2, 0) + y_shunt(2, 1)) / 6.0; + + // symmetric + DoubleComplex const yff1 = y1_series + 0.5 * y1_shunt; + DoubleComplex const yft1 = -y1_series; + DoubleComplex const ys1 = 0.5 * y1_shunt + 1.0 / (1.0 / y1_series + 2.0 / y1_shunt); + + // asymmetric + ComplexTensor ytt = y_series + 0.5 * y_shunt; + ComplexTensor branch_shunt = 0.5 * y_shunt + inv(inv(y_series) + 2.0 * inv(y_shunt)); + + double constexpr nominal_current = 216.0; + DoubleComplex const u1f = 1.0; + DoubleComplex const u1t = 0.9; + ComplexValue const uaf{1.0}; + ComplexValue const uat{0.9}; + + // Symmetric results + DoubleComplex const i1f = (yff1 * u1f + yft1 * u1t) * base_i; + DoubleComplex const i1t = (yft1 * u1f + yff1 * u1t) * base_i; + DoubleComplex const s_f = conj(i1f) * u1f * 10e3 * sqrt3; + DoubleComplex const s_t = conj(i1t) * u1t * 10e3 * sqrt3; + double const loading_sym = std::max(cabs(i1f), cabs(i1t)) / nominal_current; + + // Asymmetric results + ComplexValue i_f = dot(ytt, uaf) + dot(-y_series, uat); + ComplexValue i_t = dot(-y_series, uaf) + dot(ytt, uat); + ComplexValue const i3pf = base_i * cabs(i_f); + ComplexValue const i3pt = base_i * cabs(i_t); + + ComplexValue s_f_asym = uaf * conj(i_f); + ComplexValue s_t_asym = uat * conj(i_t); + + RealValue i_from_asym = base_i * cabs(i_f); + RealValue i_to_asym = base_i * cabs(i_t); + + ComplexValue const p3pf = base_power * real(s_f_asym); + ComplexValue const p3pt = base_power * real(s_t_asym); + ComplexValue const q3pf = base_power * imag(s_f_asym); + ComplexValue const q3pt = base_power * imag(s_t_asym); + + double const max_i = std::max(max_val(i_from_asym), max_val(i_to_asym)); + double const loading_asym = max_i / nominal_current; + // Short circuit results + DoubleComplex const if_sc{1.0, 1.0}; + DoubleComplex const it_sc{2.0, 2.0 * sqrt(3)}; + ComplexValue const if_sc_asym{1.0 + 1.0i}; + ComplexValue const it_sc_asym{2.0 + (2.0i * sqrt(3))}; + + CHECK(asym_line.math_model_type() == ComponentType::branch); + + SUBCASE("Voltge error") { CHECK_THROWS_AS(AsymLine(input, 50.0, 10.0e3, 50.0e3), ConflictVoltage); } + + SUBCASE("General") { + CHECK(branch.from_node() == 2); + CHECK(branch.to_node() == 3); + CHECK(branch.from_status() == true); + CHECK(branch.to_status() == true); + CHECK(branch.branch_status() == true); + CHECK(branch.status(BranchSide::from) == branch.from_status()); + CHECK(branch.status(BranchSide::to) == branch.to_status()); + CHECK(branch.base_i_from() == doctest::Approx(base_i)); + CHECK(branch.base_i_to() == doctest::Approx(base_i)); + CHECK(branch.phase_shift() == 0.0); + CHECK(!branch.is_param_mutable()); + } + + SUBCASE("Symmetric parameters") { + // double connected + BranchCalcParam param = branch.calc_param(); + CHECK(cabs(param.yff() - yff1) < numerical_tolerance); + CHECK(cabs(param.ytt() - yff1) < numerical_tolerance); + CHECK(cabs(param.ytf() - yft1) < numerical_tolerance); + CHECK(cabs(param.yft() - yft1) < numerical_tolerance); + // to connected + CHECK(branch.update(BranchUpdate{1, false, na_IntS}).topo); + param = branch.calc_param(); + CHECK(cabs(param.yff() - 0.0) < numerical_tolerance); + CHECK(cabs(param.ytt() - ys1) < numerical_tolerance); + CHECK(cabs(param.ytf() - 0.0) < numerical_tolerance); + CHECK(cabs(param.yft() - 0.0) < numerical_tolerance); + // not connected + CHECK(branch.set_status(na_IntS, false)); + param = branch.calc_param(); + CHECK(cabs(param.yff() - 0.0) < numerical_tolerance); + CHECK(cabs(param.ytt() - 0.0) < numerical_tolerance); + CHECK(cabs(param.ytf() - 0.0) < numerical_tolerance); + CHECK(cabs(param.yft() - 0.0) < numerical_tolerance); + // not changing + CHECK(!branch.set_status(false, false)); + // from connected + CHECK(branch.set_status(true, na_IntS)); + param = branch.calc_param(); + CHECK(cabs(param.yff() - ys1) < numerical_tolerance); + CHECK(cabs(param.ytt() - 0.0) < numerical_tolerance); + CHECK(cabs(param.ytf() - 0.0) < numerical_tolerance); + CHECK(cabs(param.yft() - 0.0) < numerical_tolerance); + } + + SUBCASE("Asymmetric parameters") { + // double connected + BranchCalcParam param = asym_line.calc_param(); + CHECK((cabs(param.yff() - ytt) < numerical_tolerance).all()); + CHECK((cabs(param.ytt() - ytt) < numerical_tolerance).all()); + CHECK((cabs(param.ytf() - (-y_series)) < numerical_tolerance).all()); + CHECK((cabs(param.yft() - (-y_series)) < numerical_tolerance).all()); + // no source + param = branch.calc_param(false); + CHECK((cabs(param.yff() - 0.0) < numerical_tolerance).all()); + CHECK((cabs(param.ytt() - 0.0) < numerical_tolerance).all()); + CHECK((cabs(param.ytf() - 0.0) < numerical_tolerance).all()); + CHECK((cabs(param.yft() - 0.0) < numerical_tolerance).all()); + // from connected + CHECK(branch.set_status(na_IntS, false)); + param = asym_line.calc_param(); + CHECK((cabs(param.yff() - branch_shunt) < numerical_tolerance).all()); // Fail + CHECK((cabs(param.ytt() - 0.0) < numerical_tolerance).all()); + CHECK((cabs(param.ytf() - 0.0) < numerical_tolerance).all()); + CHECK((cabs(param.yft() - 0.0) < numerical_tolerance).all()); + } + + SUBCASE("Symmetric results") { + BranchOutput output = branch.get_output(1.0, 0.9); + CHECK(output.id == 1); + CHECK(output.energized); + CHECK(output.loading == doctest::Approx(loading_sym)); + CHECK(output.i_from == doctest::Approx(cabs(i1f))); + CHECK(output.i_to == doctest::Approx(cabs(i1t))); + CHECK(output.s_from == doctest::Approx(cabs(s_f))); + CHECK(output.s_to == doctest::Approx(cabs(s_t))); + CHECK(output.p_from == doctest::Approx(real(s_f))); + CHECK(output.p_to == doctest::Approx(real(s_t))); + CHECK(output.q_from == doctest::Approx(imag(s_f))); + CHECK(output.q_to == doctest::Approx(imag(s_t))); + } + + SUBCASE("Symmetric results with direct power and current output") { + BranchSolverOutput branch_solver_output{}; + branch_solver_output.i_f = 1.0 - 2.0i; + branch_solver_output.i_t = 2.0 - 1.0i; + branch_solver_output.s_f = 1.0 - 1.5i; + branch_solver_output.s_t = 1.5 - 1.5i; + BranchOutput output = branch.get_output(branch_solver_output); + CHECK(output.id == 1); + CHECK(output.energized); + CHECK(output.loading == doctest::Approx(cabs(2.0 - 1.0i) * base_i / input.i_n)); + CHECK(output.i_from == doctest::Approx(cabs(1.0 - 2.0i) * base_i)); + CHECK(output.i_to == doctest::Approx(cabs(2.0 - 1.0i) * base_i)); + CHECK(output.s_from == doctest::Approx(cabs(1.0 - 1.5i) * base_power)); + CHECK(output.s_to == doctest::Approx(cabs(1.5 - 1.5i) * base_power)); + CHECK(output.p_from == doctest::Approx(1.0 * base_power)); + CHECK(output.p_to == doctest::Approx(1.5 * base_power)); + CHECK(output.q_from == doctest::Approx(-1.5 * base_power)); + CHECK(output.q_to == doctest::Approx(-1.5 * base_power)); + } + + SUBCASE("No source results") { + BranchOutput output = branch.get_null_output(); + CHECK(output.id == 1); + CHECK(!output.energized); + CHECK(output.loading == 0.0); + CHECK(output.i_from(0) == 0.0); + CHECK(output.i_to(1) == 0.0); + CHECK(output.s_from(2) == 0.0); + CHECK(output.s_to(0) == 0.0); + CHECK(output.p_from(1) == 0.0); + CHECK(output.p_to(2) == 0.0); + CHECK(output.q_from(0) == 0.0); + CHECK(output.q_to(1) == 0.0); + } + + SUBCASE("No source short circuit results") { + BranchShortCircuitOutput output = branch.get_null_sc_output(); + CHECK(output.id == 1); + CHECK(!output.energized); + CHECK(output.i_from(0) == 0.0); + CHECK(output.i_to(1) == 0.0); + CHECK(output.i_from_angle(0) == 0.0); + CHECK(output.i_to_angle(1) == 0.0); + } + + SUBCASE("Asymmetric results") { + BranchOutput output = branch.get_output(uaf, uat); + CHECK(output.id == 1); + CHECK(output.energized); + CHECK(output.loading == doctest::Approx(loading_asym)); + CHECK((cabs(output.i_from - i3pf) < numerical_tolerance).all()); + CHECK((cabs(output.i_to - i3pt) < numerical_tolerance).all()); + CHECK((cabs(output.p_from - p3pf) < numerical_tolerance).all()); + CHECK((cabs(output.p_to - p3pt) < numerical_tolerance).all()); + CHECK((cabs(output.q_from - q3pf) < numerical_tolerance).all()); + CHECK((cabs(output.q_to - q3pt) < numerical_tolerance).all()); + } + + SUBCASE("Asym short circuit results") { + BranchShortCircuitOutput asym_output = branch.get_sc_output(if_sc_asym, it_sc_asym); + CHECK(asym_output.id == 1); + CHECK(asym_output.energized); + CHECK(asym_output.i_from(1) == doctest::Approx(cabs(if_sc) * base_i)); + CHECK(asym_output.i_from(2) == doctest::Approx(cabs(if_sc) * base_i)); + CHECK(asym_output.i_to(0) == doctest::Approx(cabs(it_sc) * base_i)); + CHECK(asym_output.i_to(1) == doctest::Approx(cabs(it_sc) * base_i)); + CHECK(asym_output.i_from_angle(0) == doctest::Approx(pi / 4)); + CHECK(asym_output.i_from_angle(2) == doctest::Approx(pi / 4 + deg_120)); + CHECK(asym_output.i_to_angle(1) == doctest::Approx(pi / 3 - deg_120)); + CHECK(asym_output.i_to_angle(2) == doctest::Approx(pi / 3 + deg_120)); + CHECK(asym_output.id == 1); + } + + SUBCASE("Sym short circuit results") { + BranchShortCircuitOutput sym_output = branch.get_sc_output(if_sc, it_sc); + BranchShortCircuitOutput asym_output = branch.get_sc_output(if_sc_asym, it_sc_asym); + CHECK(sym_output.energized == asym_output.energized); + CHECK(sym_output.i_from(1) == doctest::Approx(asym_output.i_from(1))); + CHECK(sym_output.i_from(2) == doctest::Approx(asym_output.i_from(2))); + CHECK(sym_output.i_to(0) == doctest::Approx(asym_output.i_to(0))); + CHECK(sym_output.i_to(1) == doctest::Approx(asym_output.i_to(1))); + CHECK(sym_output.i_from_angle(0) == doctest::Approx(asym_output.i_from_angle(0))); + CHECK(sym_output.i_from_angle(2) == doctest::Approx(asym_output.i_from_angle(2))); + CHECK(sym_output.i_to_angle(1) == doctest::Approx(asym_output.i_to_angle(1))); + CHECK(sym_output.i_to_angle(2) == doctest::Approx(asym_output.i_to_angle(2))); + } + + SUBCASE("Update inverse") { + BranchUpdate branch_update{1, na_IntS, na_IntS}; + auto expected = branch_update; + + SUBCASE("Identical") { + // default values + } + + SUBCASE("From status") { + SUBCASE("same") { branch_update.from_status = static_cast(asym_line.from_status()); } + SUBCASE("different") { branch_update.from_status = IntS{0}; } + expected.from_status = static_cast(asym_line.from_status()); + } + + SUBCASE("To status") { + SUBCASE("same") { branch_update.to_status = static_cast(asym_line.to_status()); } + SUBCASE("different") { branch_update.to_status = IntS{0}; } + expected.to_status = static_cast(asym_line.to_status()); + } + + SUBCASE("multiple") { + branch_update.from_status = IntS{0}; + branch_update.to_status = IntS{0}; + expected.from_status = static_cast(asym_line.from_status()); + expected.to_status = static_cast(asym_line.to_status()); + } + + auto const inv = asym_line.inverse(branch_update); + + CHECK(inv.id == expected.id); + CHECK(inv.from_status == expected.from_status); + CHECK(inv.to_status == expected.to_status); + } +} + +TEST_CASE("Test asym line") { + + double system_frequency = 50.0; + double voltage_lvl = 10.0e3; + double const base_i = base_power_1p / (voltage_lvl / sqrt3); + double const base_y = base_i * base_i / base_power_1p; + + SUBCASE("R and X matrix c0, c1 including neutral") { + AsymLineInput input = {.id = 1, + .from_node = 2, + .to_node = 3, + .from_status = 1, + .to_status = 1, + .r_aa = 0.4369, + .r_ba = 0.0496, + .r_bb = 0.4369, + .r_ca = 0.0485, + .r_cb = 0.0496, + .r_cc = 0.4369, + .r_na = 0.0496, + .r_nb = 0.0485, + .r_nc = 0.0496, + .r_nn = 0.4369, + .x_aa = 0.8538, + .x_ba = 0.7886, + .x_bb = 0.8538, + .x_ca = 0.7663, + .x_cb = 0.7886, + .x_cc = 0.8538, + .x_na = 0.7886, + .x_nb = 0.7663, + .x_nc = 0.7886, + .x_nn = 0.8538, + .c0 = 0.18, + .c1 = 0.308, + .i_n = 216.0}; + + ComplexTensor4 r_matrix = ComplexTensor4(input.r_aa, input.r_bb, input.r_cc, input.r_nn, input.r_ba, input.r_ca, + input.r_na, input.r_cb, input.r_nb, input.r_nc); + ComplexTensor4 x_matrix = ComplexTensor4(input.x_aa, input.x_bb, input.x_cc, input.x_nn, input.x_ba, input.x_ca, + input.x_na, input.x_cb, input.x_nb, input.x_nc); + ComplexTensor const c_matrix = + ComplexTensor{(2.0 * input.c1 + input.c0) / 3.0, (input.c0 - input.c1) / 3.0}; + ComplexTensor4 z = r_matrix + 1.0i * x_matrix; + + ComplexTensor const y_series = 1 / base_y * inv(kron_reduction(z)); + execute_subcases(input, y_series, c_matrix, base_i, base_y, system_frequency, voltage_lvl); + } + + SUBCASE("R and X matrix, c0, c1 excluding neutral") { + AsymLineInput input = {.id = 1, + .from_node = 2, + .to_node = 3, + .from_status = 1, + .to_status = 1, + .r_aa = 0.4369, + .r_ba = 0.0496, + .r_bb = 0.4369, + .r_ca = 0.0485, + .r_cb = 0.0496, + .r_cc = 0.4369, + .x_aa = 0.8538, + .x_ba = 0.7886, + .x_bb = 0.8538, + .x_ca = 0.7663, + .x_cb = 0.7886, + .x_cc = 0.8538, + .c0 = 0.18, + .c1 = 0.308, + .i_n = 216.0}; + ComplexTensor r_matrix = + ComplexTensor(input.r_aa, input.r_bb, input.r_cc, input.r_ba, input.r_ca, input.r_cb); + ComplexTensor x_matrix = + ComplexTensor(input.x_aa, input.x_bb, input.x_cc, input.x_ba, input.x_ca, input.x_cb); + ComplexTensor const y_series = 1 / base_y * inv(r_matrix + 1.0i * x_matrix); + ComplexTensor const c_matrix = + ComplexTensor{(2.0 * input.c1 + input.c0) / 3.0, (input.c0 - input.c1) / 3.0}; + execute_subcases(input, y_series, c_matrix, base_i, base_y, system_frequency, voltage_lvl); + } + + SUBCASE("R, X excluding neutral C matrix") { + AsymLineInput input = {.id = 1, + .from_node = 2, + .to_node = 3, + .from_status = 1, + .to_status = 1, + .r_aa = 0.4369, + .r_ba = 0.0496, + .r_bb = 0.4369, + .r_ca = 0.0485, + .r_cb = 0.0496, + .r_cc = 0.4369, + .x_aa = 0.8538, + .x_ba = 0.7886, + .x_bb = 0.8538, + .x_ca = 0.7663, + .x_cb = 0.7886, + .x_cc = 0.8538, + .c_aa = 0.3200, + .c_ba = 0.5400, + .c_bb = 0.3200, + .c_ca = 0.7600, + .c_cb = 0.5400, + .c_cc = 0.3200, + .i_n = 216.0}; + ComplexTensor r_matrix = + ComplexTensor(input.r_aa, input.r_bb, input.r_cc, input.r_ba, input.r_ca, input.r_cb); + ComplexTensor x_matrix = + ComplexTensor(input.x_aa, input.x_bb, input.x_cc, input.x_ba, input.x_ca, input.x_cb); + ComplexTensor const y_series = 1 / base_y * inv(r_matrix + 1.0i * x_matrix); + ComplexTensor const c_matrix = + ComplexTensor(input.c_aa, input.c_bb, input.c_cc, input.c_ba, input.c_ca, input.c_cb); + execute_subcases(input, y_series, c_matrix, base_i, base_y, system_frequency, voltage_lvl); + } +} + +} // namespace power_grid_model diff --git a/tests/data/power_flow/asym-line/rmatrix-with-n-xmatrix-with-n-c1-c0/asym_output.json b/tests/data/power_flow/asym-line/rmatrix-with-n-xmatrix-with-n-c1-c0/asym_output.json new file mode 100644 index 000000000..864acaef7 --- /dev/null +++ b/tests/data/power_flow/asym-line/rmatrix-with-n-xmatrix-with-n-c1-c0/asym_output.json @@ -0,0 +1,15 @@ +{ + "version": "1.0", + "type": "asym_output", + "is_batch": false, + "attributes": {}, + "data": { + "node": [ + {"id": 1, "u": [5773.5026918763415, 5773.5026918564045, 5773.5026918364692], "u_angle": [3.4046305427932281e-10, -2.0943951021629377, 2.0943951025132481]}, + {"id": 2, "u": [5773.5194556240276, 5773.2545099492745, 5772.9688100988906], "u_angle": [7.317615894109981e-07, -2.0943290868542626, 2.0944300235979698]} + ], + "asym_line": [ + {"id": 3, "p_from": [999.99779026860438, 2000.2178719222172, 3000.3820961402121], "q_from": [994.34152753320836, 1994.2992025966887, 2994.5180088008474], "p_to": [-1000.0000000283295, -1999.9999999920947, -2999.9999996775377], "q_to": [-1000.0000000355961, -1999.9999999942308, -2999.9999996714546]} + ] + } +} \ No newline at end of file diff --git a/tests/data/power_flow/asym-line/rmatrix-with-n-xmatrix-with-n-c1-c0/asym_output.json.license b/tests/data/power_flow/asym-line/rmatrix-with-n-xmatrix-with-n-c1-c0/asym_output.json.license new file mode 100644 index 000000000..e94173be3 --- /dev/null +++ b/tests/data/power_flow/asym-line/rmatrix-with-n-xmatrix-with-n-c1-c0/asym_output.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 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/asym-line/rmatrix-with-n-xmatrix-with-n-c1-c0/input.json b/tests/data/power_flow/asym-line/rmatrix-with-n-xmatrix-with-n-c1-c0/input.json new file mode 100644 index 000000000..fa21f8aa8 --- /dev/null +++ b/tests/data/power_flow/asym-line/rmatrix-with-n-xmatrix-with-n-c1-c0/input.json @@ -0,0 +1,70 @@ +{ + "version": "1.0", + "type": "input", + "is_batch": false, + "attributes": {}, + "data": { + "node": [ + { + "id": 1, + "u_rated": 10000.0 + }, + { + "id": 2, + "u_rated": 10000.0 + } + ], + "asym_line": [ + { + "id": 3, + "from_node": 1, + "to_node": 2, + "from_status": 1, + "to_status": 1, + "r_aa": 0.6904, + "r_ba": 0.0495, + "r_bb": 0.6904, + "r_ca": 0.0492, + "r_cb": 0.0495, + "r_cc": 0.6904, + "r_na": 0.0495, + "r_nb": 0.0492, + "r_nc": 0.0495, + "r_nn": 0.6904, + "x_aa": 0.8316, + "x_ba": 0.7559, + "x_bb": 0.8316, + "x_ca": 0.7339, + "x_cb": 0.7559, + "x_cc": 0.8316, + "x_na": 0.7559, + "x_nb": 0.7339, + "x_nc": 0.7559, + "x_nn": 0.8316, + "c0" : 0.32e-9, + "c1" : 0.54e-9, + "i_n" : 115.0 + } + ], + "source": [ + { + "id": 4, + "node": 1, + "status": 1, + "u_ref": 1.0, + "rx_ratio": 6.0, + "sk": 1e15 + } + ], + "asym_load": [ + { + "id": 5, + "node": 2, + "status": 1, + "type": 0, + "p_specified": [1000.0, 2000.0, 3000.0], + "q_specified": [1000.0, 2000.0, 3000.0] + } + ] + } +} \ No newline at end of file diff --git a/tests/data/power_flow/asym-line/rmatrix-with-n-xmatrix-with-n-c1-c0/input.json.license b/tests/data/power_flow/asym-line/rmatrix-with-n-xmatrix-with-n-c1-c0/input.json.license new file mode 100644 index 000000000..e94173be3 --- /dev/null +++ b/tests/data/power_flow/asym-line/rmatrix-with-n-xmatrix-with-n-c1-c0/input.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 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/asym-line/rmatrix-with-n-xmatrix-with-n-c1-c0/params.json b/tests/data/power_flow/asym-line/rmatrix-with-n-xmatrix-with-n-c1-c0/params.json new file mode 100644 index 000000000..adee6ea00 --- /dev/null +++ b/tests/data/power_flow/asym-line/rmatrix-with-n-xmatrix-with-n-c1-c0/params.json @@ -0,0 +1,5 @@ +{ + "calculation_method": ["newton_raphson"], + "rtol": 1e-08, + "atol": 1e-06 +} \ No newline at end of file diff --git a/tests/data/power_flow/asym-line/rmatrix-with-n-xmatrix-with-n-c1-c0/params.json.license b/tests/data/power_flow/asym-line/rmatrix-with-n-xmatrix-with-n-c1-c0/params.json.license new file mode 100644 index 000000000..e94173be3 --- /dev/null +++ b/tests/data/power_flow/asym-line/rmatrix-with-n-xmatrix-with-n-c1-c0/params.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 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/asym-line/rmatrix-with-n-xmatrix-with-n-c1-c0/test.dss b/tests/data/power_flow/asym-line/rmatrix-with-n-xmatrix-with-n-c1-c0/test.dss new file mode 100644 index 000000000..cc8d42c49 --- /dev/null +++ b/tests/data/power_flow/asym-line/rmatrix-with-n-xmatrix-with-n-c1-c0/test.dss @@ -0,0 +1,22 @@ +Clear + +Set DefaultBaseFrequency=50 + +! Swing or Source Bar +New circuit.test_1 phases=3 pu=1.0 basekv=10 bus1=Node1.1.2.3.4 MVAsc3=1000000000 MVAsc1=1000000000.0 x1r1=0.166666 x0r0=0.166666 + +! LineCodes +New LineCode.LineCode1 nphases=4 C1=0.54 C0=0.32 Normamps=115.0 Units=km +~ Rmatrix = [0.6904 |0.0495 0.6904 |0.0492 0.0495 0.6904 | 0.0495 0.0492 0.0495 0.6904] +~ Xmatrix = [0.8316 |0.7559 0.8316 |0.7339 0.7559 0.8316 | 0.7559 0.7339 0.7559 0.8316] +~ Neutral=4 Kron=yes ! eliminate the neutral conductor (4) + +! Lines +New Line.Cable1 Phases=4 Bus1=Node1.1.2.3.4 Bus2=Node2.1.2.3.4 LineCode=LineCode1 Length=1.0 Units=km + +! Load Definitions +New Load.Load1_1_Ph1 Bus1=Node2.1 Phases=1 Conn=wye Model=1 kV=5.78034 kW=1 kvar=1.0 Vmaxpu=2.0 Vminpu=0.1 +New Load.Load1_1_Ph2 Bus1=Node2.2 Phases=1 Conn=wye Model=1 kV=5.78034 kW=2 kvar=2.0 Vmaxpu=2.0 Vminpu=0.1 +New Load.Load1_1_Ph3 Bus1=Node2.3 Phases=1 Conn=wye Model=1 kV=5.78034 kW=3 kvar=3.0 Vmaxpu=2.0 Vminpu=0.1 + +set algorithm=Normal \ No newline at end of file diff --git a/tests/data/power_flow/asym-line/rmatrix-with-n-xmatrix-with-n-c1-c0/test.dss.license b/tests/data/power_flow/asym-line/rmatrix-with-n-xmatrix-with-n-c1-c0/test.dss.license new file mode 100644 index 000000000..e94173be3 --- /dev/null +++ b/tests/data/power_flow/asym-line/rmatrix-with-n-xmatrix-with-n-c1-c0/test.dss.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 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/asym-line/rmatrix-xmatrix-c1-c0/asym_output.json b/tests/data/power_flow/asym-line/rmatrix-xmatrix-c1-c0/asym_output.json new file mode 100644 index 000000000..612d1b579 --- /dev/null +++ b/tests/data/power_flow/asym-line/rmatrix-xmatrix-c1-c0/asym_output.json @@ -0,0 +1,15 @@ +{ + "version": "1.0", + "type": "asym_output", + "is_batch": false, + "attributes": {}, + "data": { + "node": [ + {"id": 1, "u": [5773.5026918763415, 5773.5026918564035, 5773.5026918364711], "u_angle": [3.4046287910948241e-10, -2.0943951021629377, 2.094395102513249]}, + {"id": 2, "u": [5773.6782851170274, 5773.0425385960079, 5773.0283807251462], "u_angle": [2.8524357194341749e-05, -2.0943193872553079, 2.0943923009436936]} + ], + "asym_line": [ + {"id": 3, "p_from": [999.99802558297233, 2000.3106979058703, 3000.2380241547926], "q_from": [994.28617988625012, 1994.3532941447736, 2994.6002124698698], "p_to": [-1000.0000000681331, -1999.9999997831299, -2999.9999998226813], "q_to": [-1000.0000000671665, -1999.9999998075646, -2999.9999998168537]} + ] + } +} \ No newline at end of file diff --git a/tests/data/power_flow/asym-line/rmatrix-xmatrix-c1-c0/asym_output.json.license b/tests/data/power_flow/asym-line/rmatrix-xmatrix-c1-c0/asym_output.json.license new file mode 100644 index 000000000..e94173be3 --- /dev/null +++ b/tests/data/power_flow/asym-line/rmatrix-xmatrix-c1-c0/asym_output.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 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/asym-line/rmatrix-xmatrix-c1-c0/input.json b/tests/data/power_flow/asym-line/rmatrix-xmatrix-c1-c0/input.json new file mode 100644 index 000000000..570c2b2d4 --- /dev/null +++ b/tests/data/power_flow/asym-line/rmatrix-xmatrix-c1-c0/input.json @@ -0,0 +1,62 @@ +{ + "version": "1.0", + "type": "input", + "is_batch": false, + "attributes": {}, + "data": { + "node": [ + { + "id": 1, + "u_rated": 10000.0 + }, + { + "id": 2, + "u_rated": 10000.0 + } + ], + "asym_line": [ + { + "id": 3, + "from_node": 1, + "to_node": 2, + "from_status": 1, + "to_status": 1, + "r_aa": 0.6904, + "r_ba": 0.0495, + "r_bb": 0.6904, + "r_ca": 0.0492, + "r_cb": 0.0495, + "r_cc": 0.6904, + "x_aa": 0.8316, + "x_ba": 0.7559, + "x_bb": 0.8316, + "x_ca": 0.7339, + "x_cb": 0.7559, + "x_cc": 0.8316, + "c0" : 0.32e-9, + "c1" : 0.54e-9, + "i_n" : 115.0 + } + ], + "source": [ + { + "id": 4, + "node": 1, + "status": 1, + "u_ref": 1.0, + "rx_ratio": 6.0, + "sk": 1e15 + } + ], + "asym_load": [ + { + "id": 5, + "node": 2, + "status": 1, + "type": 0, + "p_specified": [1000.0, 2000.0, 3000.0], + "q_specified": [1000.0, 2000.0, 3000.0] + } + ] + } +} \ No newline at end of file diff --git a/tests/data/power_flow/asym-line/rmatrix-xmatrix-c1-c0/input.json.license b/tests/data/power_flow/asym-line/rmatrix-xmatrix-c1-c0/input.json.license new file mode 100644 index 000000000..e94173be3 --- /dev/null +++ b/tests/data/power_flow/asym-line/rmatrix-xmatrix-c1-c0/input.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 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/asym-line/rmatrix-xmatrix-c1-c0/params.json b/tests/data/power_flow/asym-line/rmatrix-xmatrix-c1-c0/params.json new file mode 100644 index 000000000..adee6ea00 --- /dev/null +++ b/tests/data/power_flow/asym-line/rmatrix-xmatrix-c1-c0/params.json @@ -0,0 +1,5 @@ +{ + "calculation_method": ["newton_raphson"], + "rtol": 1e-08, + "atol": 1e-06 +} \ No newline at end of file diff --git a/tests/data/power_flow/asym-line/rmatrix-xmatrix-c1-c0/params.json.license b/tests/data/power_flow/asym-line/rmatrix-xmatrix-c1-c0/params.json.license new file mode 100644 index 000000000..e94173be3 --- /dev/null +++ b/tests/data/power_flow/asym-line/rmatrix-xmatrix-c1-c0/params.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 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/asym-line/rmatrix-xmatrix-c1-c0/test.dss b/tests/data/power_flow/asym-line/rmatrix-xmatrix-c1-c0/test.dss new file mode 100644 index 000000000..c261c6b91 --- /dev/null +++ b/tests/data/power_flow/asym-line/rmatrix-xmatrix-c1-c0/test.dss @@ -0,0 +1,21 @@ +Clear + +Set DefaultBaseFrequency=50 + +! Swing or Source Bar +New circuit.test_1 phases=3 pu=1.0 basekv=10 bus1=Node1.1.2.3 MVAsc3=1000000000 MVAsc1=1000000000.0 x1r1=0.166666 x0r0=0.166666 + +! LineCodes +New LineCode.LineCode1 nphases=3 C1=0.54 C0=0.32 Normamps=115.0 Units=km +~ Rmatrix = [0.6904 0.0495 0.0492 | 0.0495 0.6904 0.0495 | 0.0492 0.0495 0.6904 ] +~ Xmatrix = [0.8316 0.7559 0.7339 | 0.7559 0.8316 0.7559 | 0.7339 0.7559 0.8316 ] + +! Lines +New Line.Cable1 Phases=3 Bus1=Node1.1.2.3 Bus2=Node2.1.2.3 LineCode=LineCode1 Length=1.0 Units=km + +! Load Definitions +New Load.Load1_1_Ph1 Bus1=Node2.1 Phases=1 Conn=wye Model=1 kV=5.78034 kW=1 kvar=1.0 Vmaxpu=2.0 Vminpu=0.1 +New Load.Load1_1_Ph2 Bus1=Node2.2 Phases=1 Conn=wye Model=1 kV=5.78034 kW=2 kvar=2.0 Vmaxpu=2.0 Vminpu=0.1 +New Load.Load1_1_Ph3 Bus1=Node2.3 Phases=1 Conn=wye Model=1 kV=5.78034 kW=3 kvar=3.0 Vmaxpu=2.0 Vminpu=0.1 + +set algorithm=Normal \ No newline at end of file diff --git a/tests/data/power_flow/asym-line/rmatrix-xmatrix-c1-c0/test.dss.license b/tests/data/power_flow/asym-line/rmatrix-xmatrix-c1-c0/test.dss.license new file mode 100644 index 000000000..e94173be3 --- /dev/null +++ b/tests/data/power_flow/asym-line/rmatrix-xmatrix-c1-c0/test.dss.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 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/asym-line/rmatrix-xmatrix-cmatrix/asym_output.json b/tests/data/power_flow/asym-line/rmatrix-xmatrix-cmatrix/asym_output.json new file mode 100644 index 000000000..d33e38912 --- /dev/null +++ b/tests/data/power_flow/asym-line/rmatrix-xmatrix-cmatrix/asym_output.json @@ -0,0 +1,15 @@ +{ + "version": "1.0", + "type": "asym_output", + "is_batch": false, + "attributes": {}, + "data": { + "node": [ + {"id": 1, "u": [5773.5026918763497, 5773.5026918563799, 5773.5026918364129], "u_angle": [3.4049086797073568e-10, -2.0943951021629141, 2.0943951025132748]}, + {"id": 2, "u": [5773.6784084376541, 5773.0423355653165, 5773.0282648054863], "u_angle": [2.8638259831177122e-05, -2.0943193090049426, 2.0943923605175119]} + ], + "asym_line": [ + {"id": 3, "p_from": [998.00316496718199, 2000.3107111727538, 3002.23396107193], "q_from": [1003.3961582170745, 2002.311852383297, 3003.7106905520777], "p_to": [-1000.0000000730713, -1999.9999998001122, -2999.9999998204357], "q_to": [-1000.0000000782059, -1999.9999998043552, -2999.9999998172966]} + ] + } +} \ No newline at end of file diff --git a/tests/data/power_flow/asym-line/rmatrix-xmatrix-cmatrix/asym_output.json.license b/tests/data/power_flow/asym-line/rmatrix-xmatrix-cmatrix/asym_output.json.license new file mode 100644 index 000000000..e94173be3 --- /dev/null +++ b/tests/data/power_flow/asym-line/rmatrix-xmatrix-cmatrix/asym_output.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 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/asym-line/rmatrix-xmatrix-cmatrix/input.json b/tests/data/power_flow/asym-line/rmatrix-xmatrix-cmatrix/input.json new file mode 100644 index 000000000..3b40292d6 --- /dev/null +++ b/tests/data/power_flow/asym-line/rmatrix-xmatrix-cmatrix/input.json @@ -0,0 +1,66 @@ +{ + "version": "1.0", + "type": "input", + "is_batch": false, + "attributes": {}, + "data": { + "node": [ + { + "id": 1, + "u_rated": 10000.0 + }, + { + "id": 2, + "u_rated": 10000.0 + } + ], + "asym_line": [ + { + "id": 3, + "from_node": 1, + "to_node": 2, + "from_status": 1, + "to_status": 1, + "r_aa": 0.6904, + "r_ba": 0.0495, + "r_bb": 0.6904, + "r_ca": 0.0492, + "r_cb": 0.0495, + "r_cc": 0.6904, + "x_aa": 0.8316, + "x_ba": 0.7559, + "x_bb": 0.8316, + "x_ca": 0.7339, + "x_cb": 0.7559, + "x_cc": 0.8316, + "c_aa": 0.3200e-09, + "c_ba": 0.5400e-09, + "c_bb": 0.3200e-09, + "c_ca": 0.7600e-09, + "c_cb": 0.5400e-09, + "c_cc": 0.3200e-09, + "i_n" : 115.0 + } + ], + "source": [ + { + "id": 4, + "node": 1, + "status": 1, + "u_ref": 1.0, + "rx_ratio": 6.0, + "sk": 1e15 + } + ], + "asym_load": [ + { + "id": 5, + "node": 2, + "status": 1, + "type": 0, + "p_specified": [1000.0, 2000.0, 3000.0], + "q_specified": [1000.0, 2000.0, 3000.0] + } + ] + } +} \ No newline at end of file diff --git a/tests/data/power_flow/asym-line/rmatrix-xmatrix-cmatrix/input.json.license b/tests/data/power_flow/asym-line/rmatrix-xmatrix-cmatrix/input.json.license new file mode 100644 index 000000000..e94173be3 --- /dev/null +++ b/tests/data/power_flow/asym-line/rmatrix-xmatrix-cmatrix/input.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 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/asym-line/rmatrix-xmatrix-cmatrix/params.json b/tests/data/power_flow/asym-line/rmatrix-xmatrix-cmatrix/params.json new file mode 100644 index 000000000..adee6ea00 --- /dev/null +++ b/tests/data/power_flow/asym-line/rmatrix-xmatrix-cmatrix/params.json @@ -0,0 +1,5 @@ +{ + "calculation_method": ["newton_raphson"], + "rtol": 1e-08, + "atol": 1e-06 +} \ No newline at end of file diff --git a/tests/data/power_flow/asym-line/rmatrix-xmatrix-cmatrix/params.json.license b/tests/data/power_flow/asym-line/rmatrix-xmatrix-cmatrix/params.json.license new file mode 100644 index 000000000..e94173be3 --- /dev/null +++ b/tests/data/power_flow/asym-line/rmatrix-xmatrix-cmatrix/params.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 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/asym-line/rmatrix-xmatrix-cmatrix/test.dss b/tests/data/power_flow/asym-line/rmatrix-xmatrix-cmatrix/test.dss new file mode 100644 index 000000000..d4fdf561b --- /dev/null +++ b/tests/data/power_flow/asym-line/rmatrix-xmatrix-cmatrix/test.dss @@ -0,0 +1,22 @@ +Clear + +Set DefaultBaseFrequency=50 + +! Swing or Source Bar +New circuit.test_1 phases=3 pu=1.0 basekv=10 bus1=Node1.1.2.3 MVAsc3=1000000000 MVAsc1=1000000000.0 x1r1=0.166666 x0r0=0.166666 + +! LineCodes +New LineCode.LineCode1 nphases=3 Normamps=115.0 Units=km +~ Rmatrix = [0.6904 0.0495 0.0492 | 0.0495 0.6904 0.0495 | 0.0492 0.0495 0.6904 ] +~ Xmatrix = [0.8316 0.7559 0.7339 | 0.7559 0.8316 0.7559 | 0.7339 0.7559 0.8316 ] +~ Cmatrix = [0.32 | 0.54 0.32 | 0.76 0.54 0.32 ] + +! Lines +New Line.Cable1 Phases=3 Bus1=Node1.1.2.3 Bus2=Node2.1.2.3 LineCode=LineCode1 Length=1.0 Units=km + +! Load Definitions +New Load.Load1_1_Ph1 Bus1=Node2.1 Phases=1 Conn=wye Model=1 kV=5.78034 kW=1 kvar=1.0 Vmaxpu=2.0 Vminpu=0.1 +New Load.Load1_1_Ph2 Bus1=Node2.2 Phases=1 Conn=wye Model=1 kV=5.78034 kW=2 kvar=2.0 Vmaxpu=2.0 Vminpu=0.1 +New Load.Load1_1_Ph3 Bus1=Node2.3 Phases=1 Conn=wye Model=1 kV=5.78034 kW=3 kvar=3.0 Vmaxpu=2.0 Vminpu=0.1 + +set algorithm=Normal \ No newline at end of file diff --git a/tests/data/power_flow/asym-line/rmatrix-xmatrix-cmatrix/test.dss.license b/tests/data/power_flow/asym-line/rmatrix-xmatrix-cmatrix/test.dss.license new file mode 100644 index 000000000..e94173be3 --- /dev/null +++ b/tests/data/power_flow/asym-line/rmatrix-xmatrix-cmatrix/test.dss.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 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/asym-line/status-from0-to1/asym_output.json b/tests/data/power_flow/asym-line/status-from0-to1/asym_output.json new file mode 100644 index 000000000..19e9d01ed --- /dev/null +++ b/tests/data/power_flow/asym-line/status-from0-to1/asym_output.json @@ -0,0 +1,15 @@ +{ + "version": "1.0", + "type": "asym_output", + "is_batch": false, + "attributes": {}, + "data": { + "node": [ + {"id": 1, "u": [5773.5026918962585, 5773.5026918962576, 5773.5026918962567], "u_angle": [3.380138445574585e-10, -2.0943951021678524, 2.0943951025058669]}, + {"id": 2, "u": [0, 0, 0], "u_angle": [0, 0, 0]} + ], + "asym_line": [ + {"id": 3, "p_from": [0, 0, 0], "q_from": [0, 0, 0], "p_to": [0, 0, 0], "q_to": [0, 0, 0]} + ] + } +} \ No newline at end of file diff --git a/tests/data/power_flow/asym-line/status-from0-to1/asym_output.json.license b/tests/data/power_flow/asym-line/status-from0-to1/asym_output.json.license new file mode 100644 index 000000000..e94173be3 --- /dev/null +++ b/tests/data/power_flow/asym-line/status-from0-to1/asym_output.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 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/asym-line/status-from0-to1/input.json b/tests/data/power_flow/asym-line/status-from0-to1/input.json new file mode 100644 index 000000000..cecdd44bb --- /dev/null +++ b/tests/data/power_flow/asym-line/status-from0-to1/input.json @@ -0,0 +1,66 @@ +{ + "version": "1.0", + "type": "input", + "is_batch": false, + "attributes": {}, + "data": { + "node": [ + { + "id": 1, + "u_rated": 10000.0 + }, + { + "id": 2, + "u_rated": 10000.0 + } + ], + "asym_line": [ + { + "id": 3, + "from_node": 1, + "to_node": 2, + "from_status": 0, + "to_status": 1, + "r_aa": 0.6904, + "r_ba": 0.0495, + "r_bb": 0.6904, + "r_ca": 0.0492, + "r_cb": 0.0495, + "r_cc": 0.6904, + "x_aa": 0.8316, + "x_ba": 0.7559, + "x_bb": 0.8316, + "x_ca": 0.7339, + "x_cb": 0.7559, + "x_cc": 0.8316, + "c_aa": 0.3200e-09, + "c_ba": 0.5400e-09, + "c_bb": 0.3200e-09, + "c_ca": 0.7600e-09, + "c_cb": 0.5400e-09, + "c_cc": 0.3200e-09, + "i_n" : 115.00 + } + ], + "source": [ + { + "id": 4, + "node": 1, + "status": 1, + "u_ref": 1.0, + "rx_ratio": 6.0, + "sk": 1e15 + } + ], + "asym_load": [ + { + "id": 5, + "node": 2, + "status": 1, + "type": 0, + "p_specified": [1000.0, 2000.0, 3000.0], + "q_specified": [1000.0, 2000.0, 3000.0] + } + ] + } +} \ No newline at end of file diff --git a/tests/data/power_flow/asym-line/status-from0-to1/input.json.license b/tests/data/power_flow/asym-line/status-from0-to1/input.json.license new file mode 100644 index 000000000..e94173be3 --- /dev/null +++ b/tests/data/power_flow/asym-line/status-from0-to1/input.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 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/asym-line/status-from0-to1/params.json b/tests/data/power_flow/asym-line/status-from0-to1/params.json new file mode 100644 index 000000000..adee6ea00 --- /dev/null +++ b/tests/data/power_flow/asym-line/status-from0-to1/params.json @@ -0,0 +1,5 @@ +{ + "calculation_method": ["newton_raphson"], + "rtol": 1e-08, + "atol": 1e-06 +} \ No newline at end of file diff --git a/tests/data/power_flow/asym-line/status-from0-to1/params.json.license b/tests/data/power_flow/asym-line/status-from0-to1/params.json.license new file mode 100644 index 000000000..e94173be3 --- /dev/null +++ b/tests/data/power_flow/asym-line/status-from0-to1/params.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 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/asym-line/status-from0-to1/test.dss b/tests/data/power_flow/asym-line/status-from0-to1/test.dss new file mode 100644 index 000000000..a172d9f6a --- /dev/null +++ b/tests/data/power_flow/asym-line/status-from0-to1/test.dss @@ -0,0 +1,23 @@ +Clear + +Set DefaultBaseFrequency=50 + +! Swing or Source Bar +New circuit.test_1 phases=3 pu=1.0 basekv=10 bus1=Node1.1.2.3 MVAsc3=1000000000 MVAsc1=1000000000.0 x1r1=0.166666 x0r0=0.166666 + +! LineCodes +New LineCode.LineCode1 nphases=3 Normamps=115.0 Units=km +~ Rmatrix = [0.6904 |0.0495 0.6904 |0.0492 0.0495 0.6904 ] +~ Xmatrix = [0.8316 |0.7559 0.8316 |0.7339 0.7559 0.8316 ] +~ Cmatrix = [0.32 | 0.54 0.32 | 0.76 0.54 0.32 ] + +! Lines +New Line.SwitchA Phases=3 Bus1=Node1.1.2.3 Bus2=Node2.1.2.3 Switch=yes enabled=no +New Line.Cable1 Phases=3 Bus2=Node2.1.2.3 Bus2=Node3.1.2.3 LineCode=LineCode1 Length=1.0 Units=km + +! Load Definitions +New Load.Load1_1_Ph1 Bus1=Node3.1 Phases=1 Conn=wye Model=1 kV=5.78034 kW=1 kvar=1.0 Vmaxpu=2.0 Vminpu=0.1 +New Load.Load1_1_Ph2 Bus1=Node3.2 Phases=1 Conn=wye Model=1 kV=5.78034 kW=2 kvar=2.0 Vmaxpu=2.0 Vminpu=0.1 +New Load.Load1_1_Ph3 Bus1=Node3.3 Phases=1 Conn=wye Model=1 kV=5.78034 kW=3 kvar=3.0 Vmaxpu=2.0 Vminpu=0.1 + +set algorithm=Normal \ No newline at end of file diff --git a/tests/data/power_flow/asym-line/status-from0-to1/test.dss.license b/tests/data/power_flow/asym-line/status-from0-to1/test.dss.license new file mode 100644 index 000000000..e94173be3 --- /dev/null +++ b/tests/data/power_flow/asym-line/status-from0-to1/test.dss.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 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/asym-line/status-from1-to0/asym_output.json b/tests/data/power_flow/asym-line/status-from1-to0/asym_output.json new file mode 100644 index 000000000..518607e91 --- /dev/null +++ b/tests/data/power_flow/asym-line/status-from1-to0/asym_output.json @@ -0,0 +1,15 @@ +{ + "version": "1.0", + "type": "asym_output", + "is_batch": false, + "attributes": {}, + "data": { + "node": [ + {"id": 1, "u": [5773.5026918962822, 5773.5026918962494, 5773.502691896213], "u_angle": [3.3802507065863872e-10, -2.0943951021678457, 2.0943951025058762]}, + {"id": 2, "u": [0, 0, 0], "u_angle": [0, 0, 0]} + ], + "asym_line": [ + {"id": 3, "p_from": [-1.9951793818540287, -4.758309080088452e-08, 1.9951796002629472], "q_from": [3.4557517677221288, 2.3038348140680669, 3.4557518073439577], "p_to": [1.3127424118934879e-09, -1.9691136521277103e-09, 8.4385756027700466e-09], "q_to": [2.6254852062310886e-09, -3.4106050810582259e-09, 6.3878384638406983e-09]} + ] + } +} \ No newline at end of file diff --git a/tests/data/power_flow/asym-line/status-from1-to0/asym_output.json.license b/tests/data/power_flow/asym-line/status-from1-to0/asym_output.json.license new file mode 100644 index 000000000..e94173be3 --- /dev/null +++ b/tests/data/power_flow/asym-line/status-from1-to0/asym_output.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 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/asym-line/status-from1-to0/input.json b/tests/data/power_flow/asym-line/status-from1-to0/input.json new file mode 100644 index 000000000..9883c7af7 --- /dev/null +++ b/tests/data/power_flow/asym-line/status-from1-to0/input.json @@ -0,0 +1,66 @@ +{ + "version": "1.0", + "type": "input", + "is_batch": false, + "attributes": {}, + "data": { + "node": [ + { + "id": 1, + "u_rated": 10000.0 + }, + { + "id": 2, + "u_rated": 10000.0 + } + ], + "asym_line": [ + { + "id": 3, + "from_node": 1, + "to_node": 2, + "from_status": 1, + "to_status": 0, + "r_aa": 0.6904, + "r_ba": 0.0495, + "r_bb": 0.6904, + "r_ca": 0.0492, + "r_cb": 0.0495, + "r_cc": 0.6904, + "x_aa": 0.8316, + "x_ba": 0.7559, + "x_bb": 0.8316, + "x_ca": 0.7339, + "x_cb": 0.7559, + "x_cc": 0.8316, + "c_aa": 0.3200e-09, + "c_ba": 0.5400e-09, + "c_bb": 0.3200e-09, + "c_ca": 0.7600e-09, + "c_cb": 0.5400e-09, + "c_cc": 0.3200e-09, + "i_n" : 115.0 + } + ], + "source": [ + { + "id": 4, + "node": 1, + "status": 1, + "u_ref": 1.0, + "rx_ratio": 6.0, + "sk": 1e15 + } + ], + "asym_load": [ + { + "id": 5, + "node": 2, + "status": 1, + "type": 0, + "p_specified": [1000.0, 2000.0, 3000.0], + "q_specified": [1000.0, 2000.0, 3000.0] + } + ] + } +} \ No newline at end of file diff --git a/tests/data/power_flow/asym-line/status-from1-to0/input.json.license b/tests/data/power_flow/asym-line/status-from1-to0/input.json.license new file mode 100644 index 000000000..e94173be3 --- /dev/null +++ b/tests/data/power_flow/asym-line/status-from1-to0/input.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 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/asym-line/status-from1-to0/params.json b/tests/data/power_flow/asym-line/status-from1-to0/params.json new file mode 100644 index 000000000..adee6ea00 --- /dev/null +++ b/tests/data/power_flow/asym-line/status-from1-to0/params.json @@ -0,0 +1,5 @@ +{ + "calculation_method": ["newton_raphson"], + "rtol": 1e-08, + "atol": 1e-06 +} \ No newline at end of file diff --git a/tests/data/power_flow/asym-line/status-from1-to0/params.json.license b/tests/data/power_flow/asym-line/status-from1-to0/params.json.license new file mode 100644 index 000000000..e94173be3 --- /dev/null +++ b/tests/data/power_flow/asym-line/status-from1-to0/params.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 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/asym-line/status-from1-to0/test.dss b/tests/data/power_flow/asym-line/status-from1-to0/test.dss new file mode 100644 index 000000000..fd84e931f --- /dev/null +++ b/tests/data/power_flow/asym-line/status-from1-to0/test.dss @@ -0,0 +1,23 @@ +Clear + +Set DefaultBaseFrequency=50 + +! Swing or Source Bar +New circuit.test_1 phases=3 pu=1.0 basekv=10 bus1=Node1.1.2.3 MVAsc3=1000000000 MVAsc1=1000000000.0 x1r1=0.166666 x0r0=0.166666 + +! LineCodes +New LineCode.LineCode1 nphases=3 Normamps=115.0 Units=km +~ Rmatrix = [0.6904 |0.0495 0.6904 |0.0492 0.0495 0.6904 ] +~ Xmatrix = [0.8316 |0.7559 0.8316 |0.7339 0.7559 0.8316 ] +~ Cmatrix = [0.32 | 0.54 0.32 | 0.76 0.54 0.32 ] + +! Lines +New Line.Cable1 Phases=3 Bus1=Node1.1.2.3 Bus2=Node2.1.2.3 LineCode=LineCode1 Length=1.0 Units=km +New Line.SwitchA Phases=3 Bus1=Node2.1.2.3 Bus2=Node3.1.2.3 Switch=yes enabled=no + +! Load Definitions +New Load.Load1_1_Ph1 Bus1=Node3.1 Phases=1 Conn=wye Model=1 kV=5.78034 kW=1 kvar=1.0 Vmaxpu=2.0 Vminpu=0.1 +New Load.Load1_1_Ph2 Bus1=Node3.2 Phases=1 Conn=wye Model=1 kV=5.78034 kW=2 kvar=2.0 Vmaxpu=2.0 Vminpu=0.1 +New Load.Load1_1_Ph3 Bus1=Node3.3 Phases=1 Conn=wye Model=1 kV=5.78034 kW=3 kvar=3.0 Vmaxpu=2.0 Vminpu=0.1 + +set algorithm=Normal \ No newline at end of file diff --git a/tests/data/power_flow/asym-line/status-from1-to0/test.dss.license b/tests/data/power_flow/asym-line/status-from1-to0/test.dss.license new file mode 100644 index 000000000..e94173be3 --- /dev/null +++ b/tests/data/power_flow/asym-line/status-from1-to0/test.dss.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 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 ce27ecd40..a680d7c90 100644 --- a/tests/unit/validation/test_input_validation.py +++ b/tests/unit/validation/test_input_validation.py @@ -25,6 +25,7 @@ InvalidEnumValueError, InvalidIdError, MultiComponentNotUniqueError, + MultiFieldValidationError, NotBetweenError, NotBetweenOrAtError, NotBooleanError, @@ -56,6 +57,42 @@ def original_data() -> dict[ComponentType, np.ndarray]: line["x0"] = [0, 0, 50] line["i_n"] = [-3, 0, 50] + asym_line = initialize_array(DatasetType.input, ComponentType.asym_line, 4) + asym_line["id"] = [55, 56, 57, 58] + asym_line["from_node"] = [0, 1, 0, 0] + asym_line["to_node"] = [1, 2, 2, 1] + asym_line["from_status"] = [1, 1, 1, 1] + asym_line["to_status"] = [1, 1, 1, 1] + asym_line["r_aa"] = [-1, 2, 2, 2] + asym_line["r_ba"] = [-1, 2, 2, 2] + asym_line["r_bb"] = [-1, 2, 2, 2] + asym_line["r_ca"] = [-1, 2, 2, 2] + asym_line["r_cb"] = [-1, 2, 2, 2] + asym_line["r_cc"] = [-1, 2, 2, 2] + asym_line["r_na"] = [-1, 2, 2, 2] + asym_line["r_nb"] = [-1, 2, 2, 2] + asym_line["r_nc"] = [-1, 2, 2, 2] + asym_line["r_nn"] = [-1, np.nan, 2, 2] + asym_line["x_aa"] = [-1, 2, 2, 2] + asym_line["x_ba"] = [-1, 2, 2, 2] + asym_line["x_bb"] = [-1, 2, 2, 2] + asym_line["x_ca"] = [-1, 2, 2, 2] + asym_line["x_cb"] = [-1, 2, 2, 2] + asym_line["x_cc"] = [-1, 2, 2, 2] + asym_line["x_na"] = [-1, 2, np.nan, 2] + asym_line["x_nb"] = [-1, 2, 2, 2] + asym_line["x_nc"] = [-1, 2, 2, 2] + asym_line["x_nn"] = [-1, 2, 2, 2] + asym_line["c_aa"] = [-1, np.nan, 2, np.nan] + asym_line["c_ba"] = [-1, np.nan, 2, np.nan] + asym_line["c_bb"] = [-1, np.nan, 2, np.nan] + asym_line["c_ca"] = [-1, np.nan, 2, np.nan] + asym_line["c_cb"] = [-1, np.nan, 2, np.nan] + asym_line["c_cc"] = [-1, np.nan, 2, 2] + asym_line["c0"] = [-1, np.nan, np.nan, np.nan] + asym_line["c1"] = [-1, np.nan, np.nan, np.nan] + asym_line["i_n"] = [50, 50, 50, 50] + generic_branch = initialize_array(DatasetType.input, ComponentType.generic_branch, 1) generic_branch["id"] = [6] generic_branch["from_node"] = [1] @@ -269,10 +306,10 @@ def original_data() -> dict[ComponentType, np.ndarray]: fault["fault_object"] = [200, 3] + list(range(10, 28, 2)) + 9 * [0] fault["r_f"] = [-1.0, 0.0, 1.0] + 17 * [_nan_type("fault", "r_f")] fault["x_f"] = [-1.0, 0.0, 1.0] + 17 * [_nan_type("fault", "x_f")] - data = { ComponentType.node: node, ComponentType.line: line, + ComponentType.asym_line: asym_line, ComponentType.generic_branch: generic_branch, ComponentType.link: link, ComponentType.transformer: transformer, @@ -688,3 +725,51 @@ def test_generic_branch_input_data(input_data): validation_errors = validate_input_data(input_data, symmetric=True) assert NotGreaterThanError("generic_branch", "k", [6], 0) in validation_errors assert NotGreaterOrEqualError("generic_branch", "sn", [6], 0) in validation_errors + + +def test_asym_line_input_data(input_data): + validation_errors = validate_input_data(input_data, symmetric=True) + assert NotGreaterThanError(ComponentType.asym_line, "r_aa", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "r_ba", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "r_bb", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "r_ca", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "r_cb", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "r_cc", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "r_na", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "r_nb", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "r_nc", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "r_nn", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "x_aa", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "x_ba", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "x_bb", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "x_ca", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "x_cb", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "x_cc", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "x_na", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "x_nb", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "x_nc", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "x_nn", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "c_aa", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "c_ba", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "c_bb", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "c_ca", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "c_cb", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "c_cc", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "c0", [55], 0) in validation_errors + assert NotGreaterThanError(ComponentType.asym_line, "c1", [55], 0) in validation_errors + assert ( + MultiFieldValidationError( + ComponentType.asym_line, ["r_na", "r_nb", "r_nc", "r_nn", "x_na", "x_nb", "x_nc", "x_nn"], [56, 57] + ) + in validation_errors + ) + assert ( + MultiFieldValidationError( + ComponentType.asym_line, ["c_aa", "c_ba", "c_bb", "c_ca", "c_cb", "c_cc", "c0", "c1"], [56] + ) + in validation_errors + ) + assert ( + MultiFieldValidationError(ComponentType.asym_line, ["c_aa", "c_ba", "c_bb", "c_ca", "c_cb", "c_cc"], [58]) + in validation_errors + ) diff --git a/tests/unit/validation/test_rules.py b/tests/unit/validation/test_rules.py index 97b32a093..50d7d4c2d 100644 --- a/tests/unit/validation/test_rules.py +++ b/tests/unit/validation/test_rules.py @@ -32,8 +32,10 @@ all_valid_enum_values, all_valid_fault_phases, all_valid_ids, + no_strict_subset_missing, none_match_comparison, none_missing, + not_all_missing, ) from power_grid_model.validation.errors import ( ComparisonError, @@ -43,6 +45,7 @@ InvalidIdError, MissingValueError, MultiComponentNotUniqueError, + MultiFieldValidationError, NotBetweenError, NotBetweenOrAtError, NotBooleanError, @@ -565,6 +568,100 @@ def _mock_nan_type(component: ComponentTypeLike, field: str): ) == none_missing(data=invalid, component="foo_test", fields=("foo", "bar", "baz", "bla", "ok")) +def test_no_strict_subset_missing(): + dfoo = [("id", "i4"), ("foo", "f8"), ("bar", "(3,)f8"), ("baz", "i4")] + + def _mock_nan_type(component: ComponentTypeLike, field: str): + return { + "foo_test": {"id": np.iinfo("i4").min, "foo": np.nan, "bar": np.nan, "baz": np.iinfo("i4").min}, + "bar_test": {"id": np.iinfo("i4").min, "foobar": np.nan}, + }[component][field] + + with mock.patch("power_grid_model.validation._rules._nan_type", _mock_nan_type): + valid = { + "foo_test": np.array( + [ + (1, 3.1, (4.2, 4.3, 4.4), 1), + (2, np.nan, (np.nan, np.nan, np.nan), np.iinfo("i4").min), + (3, 7.3, (8.4, 8.5, 8.6), 3), + ], + dtype=dfoo, + ) + } + errors = no_strict_subset_missing(valid, ["foo", "bar", "baz"], "foo_test") + assert len(errors) == 0 + + invalid = { + "foo_test": np.array( + [ + (1, np.nan, (np.nan, np.nan, np.nan), np.iinfo("i4").min), + (2, np.nan, (4.2, 4.3, 4.4), 3), + (3, np.nan, (np.nan, np.nan, np.nan), 1), + ], + dtype=dfoo, + ) + } + + errors = no_strict_subset_missing(invalid, ["foo", "bar", "baz"], "foo_test") + assert len(errors) == 1 + assert errors == [MultiFieldValidationError("foo_test", ["foo", "bar", "baz"], [2, 3])] + + errors = no_strict_subset_missing(invalid, ["foo", "bar"], "foo_test") + assert len(errors) == 1 + assert errors == [MultiFieldValidationError("foo_test", ["foo", "bar"], [2])] + + errors = no_strict_subset_missing(invalid, ["bar"], "foo_test") + assert len(errors) == 0 + + +def test_not_all_missing(): + dfoo = [("id", "i4"), ("foo", "f8"), ("bar", "(3,)f8"), ("baz", "i4")] + + def _mock_nan_type(component: ComponentTypeLike, field: str): + return { + "foo_test": {"id": np.iinfo("i4").min, "foo": np.nan, "bar": np.nan, "baz": np.iinfo("i4").min}, + "bar_test": {"id": np.iinfo("i4").min, "foobar": np.nan}, + }[component][field] + + with mock.patch("power_grid_model.validation._rules._nan_type", _mock_nan_type): + valid = { + "foo_test": np.array( + [ + (1, 3.1, (4.2, 4.3, 4.4), 1), + (2, 5.2, (3.3, 3.4, 3.5), 2), + (3, 7.3, (8.4, 8.5, 8.6), 3), + ], + dtype=dfoo, + ) + } + errors = not_all_missing(valid, ["foo", "bar", "baz"], "foo_test") + assert len(errors) == 0 + + invalid = { + "foo_test": np.array( + [ + (1, np.nan, (np.nan, np.nan, np.nan), np.iinfo("i4").min), + (2, np.nan, (4.2, 4.3, 4.4), 3), + (3, np.nan, (np.nan, np.nan, np.nan), 1), + ], + dtype=dfoo, + ) + } + + errors = not_all_missing(invalid, ["foo", "bar", "baz"], "foo_test") + assert len(errors) == 1 + assert errors == [MultiFieldValidationError("foo_test", ["foo", "bar", "baz"], [1])] + + with pytest.raises(ValueError) as excinfo: + not_all_missing(invalid, ["bar"], "foo_test") + + assert excinfo.type == ValueError + assert ( + str(excinfo.value) + == "The fields parameter must contain at least 2 fields. Otherwise use the none_missing function." + ) + + @pytest.mark.skip("No unit tests available for all_valid_clocks") def test_all_valid_clocks(): raise NotImplementedError(f"Unit test for {all_valid_clocks}")