diff --git a/docs/user_manual/components.md b/docs/user_manual/components.md index cee257d898..b3fd62ad8f 100644 --- a/docs/user_manual/components.md +++ b/docs/user_manual/components.md @@ -79,13 +79,13 @@ The `p` and `q` output of injection follows the `generator` reference direction | `u_angle` | `RealValueOutput` | rad | voltage angle | | `u` | `RealValueOutput` | volt (V) | voltage magnitude (line-neutral) | -## Branch +## Edge -* type name: `branch` +* type name: `edge` * base: [base](#base) -`branch` is the abstract base type for the component which connects two (possibly identical) nodes. -For each branch two switches are always defined at from- and to-side of the branch. +`edge` is the abstract base type for the component which connects two (possibly identical) nodes. +For each edge two switches are always defined at from- and to-side of the edge. In reality such switches may not exist. For example, a cable usually permanently connects two joints. In this case, the attribute `from_status` and `to_status` is always 1. @@ -122,6 +122,15 @@ In this case, the attribute `from_status` and `to_status` is always 1. | `i_to` | `RealValueOutput` | ampere (A) | magnitude of current at to-side | | `i_to_angle` | `RealValueOutput` | rad | current angle at to-side | +## Branch + +* type name: `branch` +* base: [edge](#edge) + +`branch` is the abstract base type for the component which connects two (possibly identical) nodes with finite +impedance. +All input, update and output attributes are identical to the ones for [Edges](#edge). + ### Line * type name: `line` @@ -165,19 +174,38 @@ $$ * type name: `link` -`link` is a [branch](#branch) which usually represents a short internal cable/connection -between two busbars inside a substation. -It has a very high admittance (small impedance) which is set to a fixed per-unit value (equivalent to 10e6 siemens for -10kV network). -Therefore, it is chosen by design that no sensors can be coupled to a `link`. -There is no additional attribute for `link`. +`link` is an [edge](#edge) which usually represents a short internal cable/connection between two busbars inside a +substation. +In reality, it has a very high admittance (small impedance). + +Because of this, the power-grid-model choses to model this accordingly: + +* If there are no node injection sensors in the grid, links are modeled as infinite (but equal) admittance connections. +* If there are node injection sensors in the grid, link admittances are modeled with a fixed per-unit value, equivalent + to 10e6 siemens for a 10kV network. + +```{note} +New in version [`v1.13.142`](https://github.com/PowerGridModel/power-grid-model/releases/tag/v1.13.142): links may be modeled as infinite (but equal) admittance connections. +In the old behavior, link admittances were always modeled with the same fixed per-unit value. +Starting with version `v2.0.0`, link admittances are always modeled as infinite-admittance connections. +``` + +Because links have very high admittance, it also is chosen by design that no sensors can be coupled to a `link`. +There are no additional attributes for `link`. It is explicitly allowed to connect a link between nodes with different voltage levels to allow modeling [ideal transformers](./non-native-components.md#ideal-transformer). #### Electric Model -`link` is modeled by a constant admittance $Y_{\text{series}}$, where +`link` is modeled by a constant admittance $Y_{\text{series}}$. +If there are no node injection sensors in the grid: + +$$ +Y_{\text{series}}\rightarrow \infty +$$ + +If there are node injection sensors in the grid: $$ Y_{\text{series}} = (1 + \mathrm{j}) \cdot 10^6 \,\mathrm{p.u.} 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 d30627520c..bb920e4c75 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 @@ -15,6 +15,7 @@ #include "component/branch.hpp" #include "component/branch3.hpp" #include "component/current_sensor.hpp" +#include "component/edge.hpp" #include "component/fault.hpp" #include "component/generic_branch.hpp" #include "component/line.hpp" @@ -40,7 +41,7 @@ using AllComponents = ComponentList; using AllExtraRetrievableTypes = - ExtraRetrievableTypes; } // namespace power_grid_model diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/common/enum.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/common/enum.hpp index 5d0e67c8d5..d000d41940 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/common/enum.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/common/enum.hpp @@ -68,6 +68,7 @@ enum class ComponentType : IntS { regulator = 11, transformer_tap_regulator = 12, voltage_regulator = 13, + link = 14, test = -128 // any stub or mock may use this. do not use this in production }; 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 index 0db4d44ec3..a476a3e0bf 100644 --- 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 @@ -45,7 +45,6 @@ class AsymLine : public Branch { 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_{}; @@ -103,7 +102,7 @@ class AsymLine : public Branch { BranchCalcParam asym_calc_param() const final { BranchCalcParam param{}; // not both connected - if (!branch_status()) { + if (!edge_status()) { // single connected if (from_status() || to_status()) { // branch_shunt = 0.5 * y_shunt + 1.0 / (1.0 / y_series + 2.0 / y_shunt); // NOSONAR(S125) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/component/branch.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/component/branch.hpp index 6c87faac87..77377625bd 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/component/branch.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/component/branch.hpp @@ -5,6 +5,7 @@ #pragma once #include "base.hpp" +#include "edge.hpp" #include "../auxiliary/input.hpp" #include "../auxiliary/output.hpp" @@ -25,233 +26,16 @@ namespace power_grid_model { -class Branch : public Base { +class Branch : public Edge { // TODO: not yet unit tested public: using InputType = BranchInput; using UpdateType = BranchUpdate; template using OutputType = BranchOutput; - using ShortCircuitOutputType = BranchShortCircuitOutput; - using SideType = BranchSide; static constexpr char const* name = "branch"; ComponentType math_model_type() const final { return ComponentType::branch; } - explicit Branch(BranchInput const& branch_input) - : Base{branch_input}, - from_node_{branch_input.from_node}, - to_node_{branch_input.to_node}, - from_status_{static_cast(branch_input.from_status)}, - to_status_{static_cast(branch_input.to_status)} {} - - // getter - constexpr ID from_node() const { return from_node_; } - constexpr ID to_node() const { return to_node_; } - constexpr ID node(BranchSide side) const { - switch (side) { - case BranchSide::from: - return from_node(); - case BranchSide::to: - return to_node(); - default: - throw MissingCaseForEnumError{"node(BranchSide)", side}; - } - } - constexpr bool from_status() const { return from_status_; } - constexpr bool to_status() const { return to_status_; } - constexpr bool branch_status() const { return from_status() && to_status(); } - constexpr bool status(BranchSide side) const { - switch (side) { - case BranchSide::from: - return from_status(); - case BranchSide::to: - return to_status(); - default: - throw MissingCaseForEnumError{"status(BranchSide)", side}; - } - } - template BranchCalcParam calc_param(bool is_connected_to_source = true) const { - if (!energized(is_connected_to_source)) { - return BranchCalcParam{}; - } - if constexpr (is_symmetric_v) { - return sym_calc_param(); - } else { - return asym_calc_param(); - } - } - - // virtual getter - bool energized(bool is_connected_to_source) const final { - return is_connected_to_source && (from_status_ || to_status_); - } - virtual double base_i_from() const = 0; - virtual double base_i_to() const = 0; - virtual double loading(double max_s, double max_i) const = 0; - virtual double phase_shift() const = 0; // shift theta_from - theta_to - virtual bool is_param_mutable() const = 0; - - template - BranchOutput get_output(BranchSolverOutput const& branch_solver_output) const { - // result object - BranchOutput output{}; - static_cast(output) = base_output(true); - // calculate result - output.p_from = base_power * real(branch_solver_output.s_f); - output.q_from = base_power * imag(branch_solver_output.s_f); - output.i_from = base_i_from() * cabs(branch_solver_output.i_f); - output.s_from = base_power * cabs(branch_solver_output.s_f); - output.p_to = base_power * real(branch_solver_output.s_t); - output.q_to = base_power * imag(branch_solver_output.s_t); - output.i_to = base_i_to() * cabs(branch_solver_output.i_t); - output.s_to = base_power * cabs(branch_solver_output.s_t); - double const max_s = std::max(sum_val(output.s_from), sum_val(output.s_to)); - double const max_i = std::max(max_val(output.i_from), max_val(output.i_to)); - output.loading = loading(max_s, max_i); - return output; - } - - BranchShortCircuitOutput get_sc_output(ComplexValue const& i_f, - ComplexValue const& i_t) const { - return get_sc_output(BranchShortCircuitSolverOutput{.i_f = i_f, .i_t = i_t}); - } - BranchShortCircuitOutput get_sc_output(ComplexValue const& i_f, - ComplexValue const& i_t) const { - return get_sc_output(BranchShortCircuitSolverOutput{.i_f = i_f, .i_t = i_t}); - } - - BranchShortCircuitOutput - get_sc_output(BranchShortCircuitSolverOutput const& branch_solver_output) const { - BranchShortCircuitOutput output{}; - static_cast(output) = base_output(true); - // calculate result - output.i_from = base_i_from() * cabs(branch_solver_output.i_f); - output.i_to = base_i_to() * cabs(branch_solver_output.i_t); - output.i_from_angle = arg(branch_solver_output.i_f); - output.i_to_angle = arg(branch_solver_output.i_t); - return output; - } - - BranchShortCircuitOutput - get_sc_output(BranchShortCircuitSolverOutput const& branch_solver_output) const { - return get_sc_output( - BranchShortCircuitSolverOutput{.i_f = ComplexValue{branch_solver_output.i_f}, - .i_t = ComplexValue{branch_solver_output.i_t}}); - } - - template BranchOutput get_null_output() const { - BranchOutput output{.loading = {}, - .p_from = {}, - .q_from = {}, - .i_from = {}, - .s_from = {}, - .p_to = {}, - .q_to = {}, - .i_to = {}, - .s_to = {}}; - static_cast(output) = base_output(false); - return output; - } - - BranchShortCircuitOutput get_null_sc_output() const { - BranchShortCircuitOutput output{.i_from = {}, .i_from_angle = {}, .i_to = {}, .i_to_angle = {}}; - static_cast(output) = base_output(false); - return output; - } - - // setter - bool set_status(IntS new_from_status, IntS new_to_status) { - bool const set_from = new_from_status != na_IntS; - bool const set_to = new_to_status != na_IntS; - bool changed = false; - if (set_from) { - changed = changed || (from_status_ != static_cast(new_from_status)); - from_status_ = static_cast(new_from_status); - } - if (set_to) { - changed = changed || (to_status_ != static_cast(new_to_status)); - to_status_ = static_cast(new_to_status); - } - return changed; - } - - // default update for branch, will be hidden for transformer - UpdateChange update(BranchUpdate const& update_data) { - assert(update_data.id == this->id() || is_nan(update_data.id)); - bool const changed = set_status(update_data.from_status, update_data.to_status); - // change branch connection will change both topo and param - return {.topo = changed, .param = changed}; - } - - auto inverse(std::convertible_to auto update_data) const { - assert(update_data.id == this->id() || is_nan(update_data.id)); - - set_if_not_nan(update_data.from_status, status_to_int(from_status_)); - set_if_not_nan(update_data.to_status, status_to_int(to_status_)); - - return update_data; - } - - protected: - // calculate branch param based on symmetric component - BranchCalcParam - calc_param_y_sym(DoubleComplex const& y_series, // y_series must be converted to the "to" side of the branch - DoubleComplex const& y_shunt, // y_shunt must be converted to the "to" side of the branch - DoubleComplex const& tap_ratio) const { - double const tap = cabs(tap_ratio); - BranchCalcParam param{}; - // not both connected - if (!branch_status()) { - // single connected - if (from_status() || to_status()) { - DoubleComplex branch_shunt; - // shunt value - if (cabs(y_shunt) < numerical_tolerance) { - branch_shunt = 0.0; - } else { - // branch_shunt = y_shunt/2 + 1/(1/y_series + 2/y_shunt) // NOSONAR(S125) - branch_shunt = 0.5 * y_shunt + 1.0 / (1.0 / y_series + 2.0 / y_shunt); - } - // from or to connected - param.yff() = from_status() ? (1.0 / tap / tap) * branch_shunt : 0.0; - param.ytt() = to_status() ? branch_shunt : 0.0; - } - } - // both connected - else { - param.ytt() = y_series + 0.5 * y_shunt; - param.yff() = (1.0 / tap / tap) * param.ytt(); - param.yft() = (-1.0 / conj(tap_ratio)) * y_series; - param.ytf() = (-1.0 / tap_ratio) * y_series; - } - return param; - } - // calculate branch param for asymmetric - BranchCalcParam calc_param_y_asym(DoubleComplex const& y1_series, DoubleComplex const& y1_shunt, - DoubleComplex const& y0_series, DoubleComplex const& y0_shunt, - DoubleComplex const& tap_ratio) const { - BranchCalcParam const param1 = calc_param_y_sym(y1_series, y1_shunt, tap_ratio); - BranchCalcParam const param0 = calc_param_y_sym(y0_series, y0_shunt, tap_ratio); - // abc matrix - // 1/3 * - // [[2y1+y0, y0-y1, y0-y1], - // [y0-y1, 2y1+y0, y0-y1], - // [y0-y1, y0-y1, 2y1+y0]] - BranchCalcParam param{}; - for (size_t i = 0; i < 4; ++i) { - param.value[i] = ComplexTensor{(2.0 * param1.value[i] + param0.value[i]) / 3.0, - (param0.value[i] - param1.value[i]) / 3.0}; - } - return param; - } - - private: - ID from_node_; - ID to_node_; - bool from_status_; - bool to_status_; - - virtual BranchCalcParam sym_calc_param() const = 0; - virtual BranchCalcParam asym_calc_param() const = 0; + using Edge::Edge; // inherit constructor }; } // namespace power_grid_model diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/component/component.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/component/component.hpp index 1ce8efce39..b6c67411e8 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/component/component.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/component/component.hpp @@ -51,6 +51,7 @@ static_assert(is_generator_v); // Forward declarations of all components class Base; class Node; +class Edge; class Branch; class Branch3; class Appliance; diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/component/edge.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/component/edge.hpp new file mode 100644 index 0000000000..2d514f11c2 --- /dev/null +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/component/edge.hpp @@ -0,0 +1,255 @@ +// SPDX-FileCopyrightText: Contributors to the Power Grid Model project +// +// SPDX-License-Identifier: MPL-2.0 + +#pragma once + +#include "base.hpp" + +#include "../auxiliary/input.hpp" +#include "../auxiliary/output.hpp" +#include "../auxiliary/update.hpp" +#include "../calculation_parameters.hpp" +#include "../common/common.hpp" +#include "../common/enum.hpp" +#include "../common/exception.hpp" +#include "../common/three_phase_tensor.hpp" +#include "component.hpp" + +#include + +#include +#include +#include +#include + +namespace power_grid_model { + +class Edge : public Base { + public: + using InputType = BranchInput; + using UpdateType = BranchUpdate; + template using OutputType = BranchOutput; + using ShortCircuitOutputType = BranchShortCircuitOutput; + using SideType = BranchSide; + + static constexpr char const* name = "edge"; // possibly redundant + + explicit Edge(BranchInput const& edge_input) + : Base{edge_input}, + from_node_{edge_input.from_node}, + to_node_{edge_input.to_node}, + from_status_{static_cast(edge_input.from_status)}, + to_status_{static_cast(edge_input.to_status)} {} + + // getter + constexpr ID from_node() const { return from_node_; } + constexpr ID to_node() const { return to_node_; } + constexpr ID node(BranchSide side) const { + switch (side) { + case BranchSide::from: + return from_node(); + case BranchSide::to: + return to_node(); + default: + throw MissingCaseForEnumError{"node(BranchSide)", side}; + } + } + constexpr bool from_status() const { return from_status_; } + constexpr bool to_status() const { return to_status_; } + constexpr bool edge_status() const { return from_status() && to_status(); } + constexpr bool status(BranchSide side) const { + switch (side) { + case BranchSide::from: + return from_status(); + case BranchSide::to: + return to_status(); + default: + throw MissingCaseForEnumError{"status(BranchSide)", side}; + } + } + template BranchCalcParam calc_param(bool is_connected_to_source = true) const { + // TODO(mgovers): cleanup v2: move to Branch + if (!energized(is_connected_to_source)) { + return BranchCalcParam{}; + } + if constexpr (is_symmetric_v) { + return sym_calc_param(); + } else { + return asym_calc_param(); + } + } + + // virtual getter + bool energized(bool is_connected_to_source) const final { + return is_connected_to_source && (from_status_ || to_status_); + } + virtual double base_i_from() const = 0; + virtual double base_i_to() const = 0; + virtual double loading(double max_s, double max_i) const = 0; + virtual double phase_shift() const = 0; // shift theta_from - theta_to + + template BranchOutput get_output(BranchSolverOutput const& edge_solver_output) const { + // result object + BranchOutput output{}; + static_cast(output) = base_output(true); + // calculate result + output.p_from = base_power * real(edge_solver_output.s_f); + output.q_from = base_power * imag(edge_solver_output.s_f); + output.i_from = base_i_from() * cabs(edge_solver_output.i_f); + output.s_from = base_power * cabs(edge_solver_output.s_f); + output.p_to = base_power * real(edge_solver_output.s_t); + output.q_to = base_power * imag(edge_solver_output.s_t); + output.i_to = base_i_to() * cabs(edge_solver_output.i_t); + output.s_to = base_power * cabs(edge_solver_output.s_t); + double const max_s = std::max(sum_val(output.s_from), sum_val(output.s_to)); + double const max_i = std::max(max_val(output.i_from), max_val(output.i_to)); + output.loading = loading(max_s, max_i); + return output; + } + + BranchShortCircuitOutput get_sc_output(ComplexValue const& i_f, + ComplexValue const& i_t) const { + return get_sc_output(BranchShortCircuitSolverOutput{.i_f = i_f, .i_t = i_t}); + } + BranchShortCircuitOutput get_sc_output(ComplexValue const& i_f, + ComplexValue const& i_t) const { + return get_sc_output(BranchShortCircuitSolverOutput{.i_f = i_f, .i_t = i_t}); + } + + BranchShortCircuitOutput + get_sc_output(BranchShortCircuitSolverOutput const& edge_solver_output) const { + BranchShortCircuitOutput output{}; + static_cast(output) = base_output(true); + // calculate result + output.i_from = base_i_from() * cabs(edge_solver_output.i_f); + output.i_to = base_i_to() * cabs(edge_solver_output.i_t); + output.i_from_angle = arg(edge_solver_output.i_f); + output.i_to_angle = arg(edge_solver_output.i_t); + return output; + } + + BranchShortCircuitOutput + get_sc_output(BranchShortCircuitSolverOutput const& edge_solver_output) const { + return get_sc_output( + BranchShortCircuitSolverOutput{.i_f = ComplexValue{edge_solver_output.i_f}, + .i_t = ComplexValue{edge_solver_output.i_t}}); + } + + template BranchOutput get_null_output() const { + BranchOutput output{.loading = {}, + .p_from = {}, + .q_from = {}, + .i_from = {}, + .s_from = {}, + .p_to = {}, + .q_to = {}, + .i_to = {}, + .s_to = {}}; + static_cast(output) = base_output(false); + return output; + } + + BranchShortCircuitOutput get_null_sc_output() const { + BranchShortCircuitOutput output{.i_from = {}, .i_from_angle = {}, .i_to = {}, .i_to_angle = {}}; + static_cast(output) = base_output(false); + return output; + } + + // setter + bool set_status(IntS new_from_status, IntS new_to_status) { + bool const set_from = new_from_status != na_IntS; + bool const set_to = new_to_status != na_IntS; + bool changed = false; + if (set_from) { + changed = changed || (from_status_ != static_cast(new_from_status)); + from_status_ = static_cast(new_from_status); + } + if (set_to) { + changed = changed || (to_status_ != static_cast(new_to_status)); + to_status_ = static_cast(new_to_status); + } + return changed; + } + + // default update for edge, will be hidden for transformer + UpdateChange update(BranchUpdate const& update_data) { + assert(update_data.id == this->id() || is_nan(update_data.id)); + bool const changed = set_status(update_data.from_status, update_data.to_status); + // change edge connection will change both topo and param + return {.topo = changed, .param = changed}; + } + + auto inverse(std::convertible_to auto update_data) const { + assert(update_data.id == this->id() || is_nan(update_data.id)); + + set_if_not_nan(update_data.from_status, status_to_int(from_status_)); + set_if_not_nan(update_data.to_status, status_to_int(to_status_)); + + return update_data; + } + + protected: + // calculate edge param based on symmetric component + BranchCalcParam + calc_param_y_sym(DoubleComplex const& y_series, // y_series must be converted to the "to" side of the edge + DoubleComplex const& y_shunt, // y_shunt must be converted to the "to" side of the edge + DoubleComplex const& tap_ratio) const { + double const tap = cabs(tap_ratio); + BranchCalcParam param{}; + // not both connected + if (!edge_status()) { + // single connected + if (from_status() || to_status()) { + DoubleComplex edge_shunt; + // shunt value + if (cabs(y_shunt) < numerical_tolerance) { + edge_shunt = 0.0; + } else { + // edge_shunt = y_shunt/2 + 1/(1/y_series + 2/y_shunt) // NOSONAR(S125) + edge_shunt = 0.5 * y_shunt + 1.0 / (1.0 / y_series + 2.0 / y_shunt); + } + // from or to connected + param.yff() = from_status() ? (1.0 / tap / tap) * edge_shunt : 0.0; + param.ytt() = to_status() ? edge_shunt : 0.0; + } + } + // both connected + else { + param.ytt() = y_series + 0.5 * y_shunt; + param.yff() = (1.0 / tap / tap) * param.ytt(); + param.yft() = (-1.0 / conj(tap_ratio)) * y_series; + param.ytf() = (-1.0 / tap_ratio) * y_series; + } + return param; + } + // calculate edge param for asymmetric + BranchCalcParam calc_param_y_asym(DoubleComplex const& y1_series, DoubleComplex const& y1_shunt, + DoubleComplex const& y0_series, DoubleComplex const& y0_shunt, + DoubleComplex const& tap_ratio) const { + BranchCalcParam const param1 = calc_param_y_sym(y1_series, y1_shunt, tap_ratio); + BranchCalcParam const param0 = calc_param_y_sym(y0_series, y0_shunt, tap_ratio); + // abc matrix + // 1/3 * + // [[2y1+y0, y0-y1, y0-y1], + // [y0-y1, 2y1+y0, y0-y1], + // [y0-y1, y0-y1, 2y1+y0]] + BranchCalcParam param{}; + for (size_t i = 0; i < 4; ++i) { + param.value[i] = ComplexTensor{(2.0 * param1.value[i] + param0.value[i]) / 3.0, + (param0.value[i] - param1.value[i]) / 3.0}; + } + return param; + } + + private: + ID from_node_; + ID to_node_; + bool from_status_; + bool to_status_; + + virtual BranchCalcParam sym_calc_param() const = 0; + virtual BranchCalcParam asym_calc_param() const = 0; +}; + +} // namespace power_grid_model diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/component/generic_branch.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/component/generic_branch.hpp index 82fb0b34f4..904c79a1e0 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/component/generic_branch.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/component/generic_branch.hpp @@ -58,7 +58,6 @@ class GenericBranch final : public Branch { double base_i_to() const override { return base_i_to_; } double loading(double max_s, double /*max_i*/) const override { return std::isnan(sn_) ? 0.0 : (max_s / sn_); }; double phase_shift() const override { return theta_; } - bool is_param_mutable() const override { return false; } private: double sn_; diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/component/line.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/component/line.hpp index 2e9ba93957..2a76c0d9cd 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/component/line.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/component/line.hpp @@ -40,7 +40,6 @@ class Line final : public Branch { double base_i_to() const override { return base_i_; } double loading(double /* max_s */, double max_i) const override { return max_i / i_n_; }; double phase_shift() const override { return 0.0; } - bool is_param_mutable() const override { return false; } private: double i_n_; diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/component/link.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/component/link.hpp index abd9e645b8..7cdbfc4900 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/component/link.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/component/link.hpp @@ -4,7 +4,7 @@ #pragma once -#include "branch.hpp" +#include "edge.hpp" #include "../auxiliary/input.hpp" #include "../auxiliary/update.hpp" @@ -13,21 +13,22 @@ namespace power_grid_model { -class Link final : public Branch { +class Link final : public Edge { public: using InputType = LinkInput; using UpdateType = BranchUpdate; static constexpr char const* name = "link"; + ComponentType math_model_type() const override { return ComponentType::link; } + explicit Link(LinkInput const& link_input, double u1, double u2) - : Branch{link_input}, base_i_from_{base_power_3p / u1 / sqrt3}, base_i_to_{base_power_3p / u2 / sqrt3} {} + : Edge{link_input}, base_i_from_{base_power_3p / u1 / sqrt3}, base_i_to_{base_power_3p / u2 / sqrt3} {} // override getter double base_i_from() const override { return base_i_from_; } double base_i_to() const override { return base_i_to_; } double loading(double /* max_s */, double /* max_i */) const override { return 0.0; }; double phase_shift() const override { return 0.0; } - bool is_param_mutable() const override { return false; } private: double base_i_from_; diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/component/transformer.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/component/transformer.hpp index ebd8d92196..8d421ab1ca 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/component/transformer.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/component/transformer.hpp @@ -95,7 +95,6 @@ class Transformer : public Branch { double loading(double max_s, double /* max_i */) const final { return max_s / sn_; }; // phase shift is theta_from - theta_to double phase_shift() const final { return clock_ * deg_30; } - bool is_param_mutable() const final { return true; } // getters constexpr IntS tap_pos() const { return tap_pos_; } constexpr BranchSide tap_side() const { return tap_side_; } diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/container_queries.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/container_queries.hpp index 1a9b51ae1c..188b0af82b 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/container_queries.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/container_queries.hpp @@ -100,8 +100,8 @@ constexpr auto get_component_citer(ComponentContainer const& components) { namespace detail { template struct topology_base_type; -template Component> struct topology_base_type { - using type = Branch; +template Component> struct topology_base_type { + using type = Edge; }; template Component> struct topology_base_type { using type = Branch3; 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 142b7ecd2b..81352f97fd 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 @@ -15,6 +15,7 @@ #include "../component/branch.hpp" #include "../component/branch3.hpp" #include "../component/current_sensor.hpp" +#include "../component/edge.hpp" #include "../component/fault.hpp" #include "../component/line.hpp" #include "../component/link.hpp" @@ -74,6 +75,10 @@ inline void add_component(ComponentContainer& components, Inputs component_input } else { emplace_component(components, id, input, u1, u2); } + } else if constexpr (std::derived_from) { + double const u1 = get_component(components, input.from_node).u_rated(); + double const u2 = get_component(components, input.to_node).u_rated(); + emplace_component(components, id, input, u1, u2); } else if constexpr (std::derived_from) { double const u1 = get_component(components, input.node_1).u_rated(); double const u2 = get_component(components, input.node_2).u_rated(); diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/main_model_type.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/main_model_type.hpp index 04d61367a0..b72a626fa6 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/main_model_type.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/main_model_type.hpp @@ -94,13 +94,13 @@ class MainModelType, ComponentLis std::tuple..., std::type_identity...>{}; static constexpr auto topology_types_tuple_v_ = - std::tuple, std::type_identity, std::type_identity, + std::tuple, std::type_identity, std::type_identity, std::type_identity, std::type_identity, std::type_identity, std::type_identity, std::type_identity, std::type_identity, std::type_identity>{}; static constexpr auto topology_connection_types_tuple_v_ = - std::tuple, std::type_identity, std::type_identity>{}; + std::tuple, std::type_identity, std::type_identity>{}; public: using TopologyTypesTuple = detail::tuple_type_identities_to_tuple_types_t Component, steady_state_solver_output_type SolverOutputType> +template Component, steady_state_solver_output_type SolverOutputType> constexpr auto output_result(Component const& branch, std::vector const& solver_output, Idx2D math_id) { using sym = decode_symmetry_v; @@ -95,7 +96,7 @@ constexpr auto output_result(Component const& branch, std::vector(solver_output[math_id.group].branch[math_id.pos]); } -template Component, short_circuit_solver_output_type SolverOutputType> +template Component, short_circuit_solver_output_type SolverOutputType> inline auto output_result(Component const& branch, std::vector const& solver_output, Idx2D math_id) { if (math_id.group == disconnected) { return branch.get_null_sc_output(); diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/state_queries.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/state_queries.hpp index 8483d99994..f6529e5eac 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/state_queries.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/state_queries.hpp @@ -11,6 +11,7 @@ #include "../common/enum.hpp" #include "../component/branch.hpp" #include "../component/branch3.hpp" +#include "../component/edge.hpp" #include "../component/node.hpp" #include "../component/regulator.hpp" #include "../component/shunt.hpp" @@ -21,7 +22,7 @@ #include namespace power_grid_model::main_core { -template ComponentType, class ComponentContainer> +template ComponentType, class ComponentContainer> requires model_component_state_c constexpr auto get_branch_nodes(MainModelState const& state, Idx topology_sequence_idx) { return state.comp_topo->branch_node_idx[topology_sequence_idx]; @@ -55,7 +56,7 @@ constexpr auto get_math_id(MainModelState const& state, Idx return state.topo_comp_coup->node[topology_sequence_idx]; } -template ComponentType, class ComponentContainer> +template ComponentType, class ComponentContainer> requires model_component_state_c constexpr auto get_math_id(MainModelState const& state, Idx topology_sequence_idx) { return state.topo_comp_coup->branch[topology_sequence_idx]; @@ -91,10 +92,10 @@ constexpr auto comp_base_sequence_cbegin(MainModelState cons return state.reduced_topology->topo_node_coup.coupling.user_nodes_to_topo_nodes.cbegin(); } -template Component, class ComponentContainer> +template Component, class ComponentContainer> requires model_component_state_c constexpr auto comp_base_sequence_cbegin(MainModelState const& state) { - return state.topo_comp_coup->branch.cbegin() + get_component_sequence_offset(state.components); + return state.topo_comp_coup->branch.cbegin() + get_component_sequence_offset(state.components); } template Component, class ComponentContainer> diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topology.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topology.hpp index c270d1f03e..f2dca1df70 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topology.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topology.hpp @@ -13,6 +13,8 @@ #include "../component/branch.hpp" #include "../component/branch3.hpp" #include "../component/current_sensor.hpp" +#include "../component/edge.hpp" +#include "../component/link.hpp" #include "../component/load_gen.hpp" #include "../component/node.hpp" #include "../component/power_sensor.hpp" @@ -49,12 +51,12 @@ constexpr void register_topology_components(ComponentContainer const& components comp_topo.n_node = get_component_size(components); } -template Component, class ComponentContainer> - requires common::component_container_c +template Component, class ComponentContainer> + requires common::component_container_c constexpr void register_topology_components(ComponentContainer const& components, ComponentTopology& comp_topo) { - apply_registration(components, comp_topo.branch_node_idx, [&components](Branch const& branch) { - return BranchIdx{get_component_sequence_idx(components, branch.from_node()), - get_component_sequence_idx(components, branch.to_node())}; + apply_registration(components, comp_topo.branch_node_idx, [&components](Edge const& edge) { + return BranchIdx{get_component_sequence_idx(components, edge.from_node()), + get_component_sequence_idx(components, edge.to_node())}; }); } @@ -199,15 +201,16 @@ constexpr void register_topology_components(ComponentContainer const& components [](Regulator const& regulator) { return regulator.regulated_object_type(); }); } -template Component, class ComponentContainer> - requires common::component_container_c +template Component, class ComponentContainer> + requires common::component_container_c constexpr void register_connections_components(ComponentContainer const& components, ComponentConnections& comp_conn) { - apply_registration(components, comp_conn.branch_connected, [](Branch const& branch) { - return BranchConnected{status_to_int(branch.from_status()), status_to_int(branch.to_status())}; + apply_registration(components, comp_conn.branch_connected, [](Edge const& edge) { + return BranchConnected{status_to_int(edge.from_status()), status_to_int(edge.to_status())}; }); - apply_registration(components, comp_conn.branch_phase_shift, - [](Branch const& branch) { return branch.phase_shift(); }); + apply_registration(components, comp_conn.branch_phase_shift, + [](Edge const& edge) { return edge.phase_shift(); }); } + template Component, class ComponentContainer> requires common::component_container_c constexpr void register_connections_components(ComponentContainer const& components, ComponentConnections& comp_conn) { @@ -229,7 +232,7 @@ constexpr void register_connections_components(ComponentContainer const& compone } // namespace detail template - requires common::component_container_c ComponentTopology construct_topology(typename ModelType::ComponentContainer const& components) { @@ -243,7 +246,7 @@ ComponentTopology construct_topology(typename ModelType::ComponentContainer cons } template - requires common::component_container_c + requires common::component_container_c ComponentConnections construct_components_connections(typename ModelType::ComponentContainer const& components) { ComponentConnections comp_conn; using TopologyConnectionTypesTuple = ModelType::TopologyConnectionTypesTuple; diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/y_bus.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/y_bus.hpp index c784fff379..47705b24a4 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/y_bus.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/y_bus.hpp @@ -24,17 +24,17 @@ constexpr Idx isolated_component{-1}; namespace detail { -template ComponentType, math_model_param_c MathModelParamType, typename ComponentContainer> +template ComponentType, math_model_param_c MathModelParamType, typename ComponentContainer> constexpr void add_to_math_model_params(std::vector& math_model_param, MainModelState const& state, Idx const topology_sequence_idx) { - Idx2D const math_idx = get_math_id(state, topology_sequence_idx); + Idx2D const math_idx = get_math_id(state, topology_sequence_idx); if (math_idx.group == isolated_component) { return; } // assign parameters auto& model_params = math_model_param[math_idx.group]; - auto branch_params = state.components.template get_item_by_seq(topology_sequence_idx) + auto branch_params = state.components.template get_item_by_seq(topology_sequence_idx) .template calc_param(); if constexpr (std::derived_from>) { @@ -123,7 +123,7 @@ constexpr void add_to_math_model_params(std::vector const& / } template - requires std::derived_from || std::derived_from || + requires std::derived_from || std::derived_from || std::derived_from || std::derived_from constexpr void add_to_increment(std::vector>& increments, MainModelState const& state, Idx2D const& changed_component_idx) { @@ -193,7 +193,7 @@ inline std::vector> get_math_param(main_model_state_c auto c } // loop all branch for (Idx const i : IdxRange{std::ssize(state.comp_topo->branch_node_idx)}) { - detail::add_to_math_model_params(math_param, state, i); + detail::add_to_math_model_params(math_param, state, i); } // loop all branch3 for (Idx const i : IdxRange{std::ssize(state.comp_topo->branch3_node_idx)}) { diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp index 142841c2ba..22f1f6ef1c 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp @@ -207,6 +207,7 @@ class MainModelImpl { construction_complete_ = true; #endif // !NDEBUG state_.components.set_construction_complete(); + state_.comp_topo = std::make_shared(main_core::construct_topology(state_.components)); } @@ -423,6 +424,8 @@ class MainModelImpl { } void check_no_future_deprecations(Options const& /*options*/, ConstDataset const* /*batch_dataset*/) const { + // TODO(mgovers): cleanup v2: remove this function, as power sensor creation itself should always throw after + // node injection sensors removal ModelType::run_functor_with_all_component_types_return_void([this]() { // only flow sensors have get_terminal_type() so we can filter on it if constexpr (requires(CT c) { diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/optimizer/tap_position_optimizer.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/optimizer/tap_position_optimizer.hpp index 87b0951cbb..820271a1fc 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/optimizer/tap_position_optimizer.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/optimizer/tap_position_optimizer.hpp @@ -16,6 +16,7 @@ #include "../component/branch.hpp" #include "../component/branch3.hpp" #include "../component/component.hpp" +#include "../component/edge.hpp" #include "../component/line.hpp" #include "../component/link.hpp" #include "../component/node.hpp" @@ -233,7 +234,7 @@ constexpr void add_edge(main_core::MainModelState const& sta } } -template Component, class ComponentContainer> +template Component, class ComponentContainer> requires main_core::model_component_state_c && (!transformer_c) constexpr void add_edge(main_core::MainModelState const& state, @@ -625,7 +626,7 @@ inline auto regulator_mapping(State const& state, RankedTransformerGroups const& return result; } -template ComponentType, steady_state_solver_output_type SolverOutputType> +template ComponentType, steady_state_solver_output_type SolverOutputType> inline auto i_pu(std::vector const& solver_output, Idx2D const& math_id, ControlSide control_side) { using enum ControlSide; diff --git a/tests/cpp_unit_tests/component/CMakeLists.txt b/tests/cpp_unit_tests/component/CMakeLists.txt index 649ce60f2e..56723f3702 100644 --- a/tests/cpp_unit_tests/component/CMakeLists.txt +++ b/tests/cpp_unit_tests/component/CMakeLists.txt @@ -7,6 +7,7 @@ add_executable( "../test_entry_point.cpp" "test_all_components.cpp" "test_node.cpp" + "test_edge.cpp" "test_asym_line.cpp" "test_line.cpp" "test_generic_branch.cpp" diff --git a/tests/cpp_unit_tests/component/test_asym_line.cpp b/tests/cpp_unit_tests/component/test_asym_line.cpp index aaf983020f..9cd0a63a7c 100644 --- a/tests/cpp_unit_tests/component/test_asym_line.cpp +++ b/tests/cpp_unit_tests/component/test_asym_line.cpp @@ -68,13 +68,12 @@ void execute_subcases(const AsymLineInput& input, const ComplexTensor +// +// SPDX-License-Identifier: MPL-2.0 + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace power_grid_model { + +using namespace std::complex_literals; + +namespace { +// Concrete test implementation of Edge for testing purposes +class TestEdge : public Edge { + public: + explicit TestEdge(BranchInput const& edge_input, double u_rated, DoubleComplex const& y_series, + DoubleComplex const& y_shunt, DoubleComplex const& tap_ratio = 1.0, + DoubleComplex const& y0_series = 0.0, DoubleComplex const& y0_shunt = 0.0) + : Edge{edge_input}, + base_i_{base_power_3p / u_rated / sqrt3}, + y_series_{y_series}, + y_shunt_{y_shunt}, + tap_ratio_{tap_ratio}, + y0_series_{y0_series}, + y0_shunt_{y0_shunt} {} + + ComponentType math_model_type() const override { return ComponentType::branch; } + + double base_i_from() const override { return base_i_; } + double base_i_to() const override { return base_i_; } + double loading(double max_s, double /*max_i*/) const override { return max_s / base_power_3p; } + double phase_shift() const override { return arg(tap_ratio_); } + + private: + double base_i_; + DoubleComplex y_series_; + DoubleComplex y_shunt_; + DoubleComplex tap_ratio_; + DoubleComplex y0_series_; + DoubleComplex y0_shunt_; + + BranchCalcParam sym_calc_param() const override { + return calc_param_y_sym(y_series_, y_shunt_, tap_ratio_); + } + + BranchCalcParam asym_calc_param() const override { + return calc_param_y_asym(y_series_, y_shunt_, y0_series_, y0_shunt_, tap_ratio_); + } +}; +} // namespace + +TEST_CASE("Test edge") { + BranchInput const input{.id = 1, .from_node = 2, .to_node = 3, .from_status = 1, .to_status = 1}; + + double const u_rated = 10.0e3; + double const base_i = base_power_3p / u_rated / sqrt3; + double const base_y = base_i * base_i / base_power_1p; + + DoubleComplex const y_series = (1.0 / (0.3 + 0.4i)) / base_y; + DoubleComplex const y_shunt = (50.0 * 2 * pi * 2e-4) * (0.1 + 1.0i) / base_y; + DoubleComplex const tap_ratio = 1.0 + 0.0i; + + // For asymmetric + DoubleComplex const y0_series = (1.0 / (0.1 + 0.2i)) / base_y; + DoubleComplex const y0_shunt = (50.0 * 2 * pi * 1e-4) * (0.2 + 1.0i) / base_y; + + TestEdge edge{input, u_rated, y_series, y_shunt, tap_ratio, y0_series, y0_shunt}; + Edge& edge_ref = edge; + + // Expected symmetric values + DoubleComplex const yff1 = y_series + 0.5 * y_shunt; + DoubleComplex const yft1 = -y_series; + DoubleComplex const ys1 = 0.5 * y_shunt + 1.0 / (1.0 / y_series + 2.0 / y_shunt); + + // Expected asymmetric values + DoubleComplex const yff0 = y0_series + 0.5 * y0_shunt; + DoubleComplex const yft0 = -y0_series; + DoubleComplex const ys0 = 0.5 * y0_shunt + 1.0 / (1.0 / y0_series + 2.0 / y0_shunt); + ComplexTensor const yffa{(2.0 * yff1 + yff0) / 3.0, (yff0 - yff1) / 3.0}; + ComplexTensor const yfta{(2.0 * yft1 + yft0) / 3.0, (yft0 - yft1) / 3.0}; + ComplexTensor const ysa{(2.0 * ys1 + ys0) / 3.0, (ys0 - ys1) / 3.0}; + + SUBCASE("Property Mapping") { + CHECK(edge_ref.id() == 1); + CHECK(edge_ref.from_node() == 2); + CHECK(edge_ref.to_node() == 3); + CHECK(edge_ref.from_status() == 1); + CHECK(edge_ref.to_status() == 1); + CHECK(Edge::name == std::string{"edge"}); + } + + SUBCASE("Status Flags") { + CHECK(edge_ref.from_status() == 1); + CHECK(edge_ref.to_status() == 1); + CHECK(edge_ref.edge_status() == 1); + + edge_ref.set_status(1, 0); + CHECK(edge_ref.edge_status() == 0); + + edge_ref.set_status(0, 1); + CHECK(edge_ref.edge_status() == 0); + + edge_ref.set_status(0, 0); + CHECK(edge_ref.edge_status() == 0); + + edge_ref.set_status(1, 1); + } + + SUBCASE("Status Updates") { + // Reset to known state + edge_ref.set_status(1, 1); + + CHECK(!edge_ref.set_status(na_IntS, na_IntS)); + CHECK(edge_ref.from_status() == 1); + CHECK(edge_ref.to_status() == 1); + + CHECK(edge_ref.set_status(0, na_IntS)); + CHECK(edge_ref.from_status() == 0); + + CHECK(!edge_ref.set_status(0, na_IntS)); + + BranchUpdate const update{.id = 1, .from_status = 1, .to_status = na_IntS}; + UpdateChange const change = edge_ref.update(update); + CHECK(change.topo == 1); + CHECK(change.param == 1); + } + + SUBCASE("Inverse State") { + // Reset to known state + edge_ref.set_status(1, 1); + + BranchUpdate const update{.id = 1, .from_status = 0, .to_status = 0}; + auto const inv = edge_ref.inverse(update); + CHECK(inv.from_status == status_to_int(1)); + CHECK(inv.to_status == status_to_int(1)); + } + + SUBCASE("Energization") { + edge_ref.set_status(1, 1); + CHECK(edge_ref.energized(0) == 0); + CHECK(edge_ref.energized(1) == 1); + + edge_ref.set_status(0, 1); + CHECK(edge_ref.energized(1) == 1); + + edge_ref.set_status(0, 0); + CHECK(edge_ref.energized(1) == 0); + + edge_ref.set_status(1, 1); + } + + SUBCASE("Symmetric Parameters") { + edge_ref.set_status(1, 1); + + // Not energized + BranchCalcParam param = edge_ref.calc_param(false); + CHECK(cabs(param.yff() - 0.0) < numerical_tolerance); + + // Both disconnected + edge_ref.set_status(0, 0); + param = edge_ref.calc_param(true); + CHECK(cabs(param.yff() - 0.0) < numerical_tolerance); + CHECK(cabs(param.ytt() - 0.0) < numerical_tolerance); + + // From connected only + edge_ref.set_status(1, 0); + param = edge_ref.calc_param(true); + CHECK(cabs(param.yff() - ys1) < numerical_tolerance); + CHECK(cabs(param.ytt() - 0.0) < numerical_tolerance); + + // To connected only + edge_ref.set_status(0, 1); + param = edge_ref.calc_param(true); + CHECK(cabs(param.yff() - 0.0) < numerical_tolerance); + CHECK(cabs(param.ytt() - ys1) < numerical_tolerance); + + // Both connected + edge_ref.set_status(1, 1); + param = edge_ref.calc_param(true); + 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); + } + + SUBCASE("Asymmetric Parameters") { + edge_ref.set_status(1, 1); + BranchCalcParam param = edge_ref.calc_param(true); + + CHECK((cabs(param.yff() - yffa) < numerical_tolerance).all()); + CHECK((cabs(param.ytt() - yffa) < numerical_tolerance).all()); + CHECK((cabs(param.ytf() - yfta) < numerical_tolerance).all()); + CHECK((cabs(param.yft() - yfta) < numerical_tolerance).all()); + + edge_ref.set_status(1, 0); + param = edge_ref.calc_param(true); + CHECK((cabs(param.yff() - ysa) < numerical_tolerance).all()); + CHECK((cabs(param.ytt() - 0.0) < numerical_tolerance).all()); + } + + SUBCASE("Output Generation") { + edge_ref.set_status(1, 1); + BranchSolverOutput const solver_output{ + .s_f = 1.0 - 1.5i, .s_t = 1.5 - 1.5i, .i_f = 1.0 - 2.0i, .i_t = 2.0 - 1.0i}; + + BranchOutput const output = edge_ref.get_output(solver_output); + + CHECK(output.id == 1); + CHECK(output.energized == 1); + CHECK(output.p_from == doctest::Approx(1.0 * base_power)); + CHECK(output.q_from == doctest::Approx(-1.5 * base_power)); + 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)); + } + + SUBCASE("Short Circuit Output") { + edge_ref.set_status(1, 1); + DoubleComplex const if_sc{1.0, 1.0}; + DoubleComplex const it_sc{2.0, 2.0 * sqrt3}; + + BranchShortCircuitOutput const sc_output = edge_ref.get_sc_output(if_sc, it_sc); + CHECK(sc_output.id == 1); + CHECK(sc_output.energized == 1); + CHECK(sc_output.i_from(0) == doctest::Approx(cabs(if_sc) * base_i)); + CHECK(sc_output.i_to(0) == doctest::Approx(cabs(it_sc) * base_i)); + CHECK(sc_output.i_from_angle(0) == doctest::Approx(pi / 4)); + CHECK(sc_output.i_to_angle(0) == doctest::Approx(pi / 3)); + } + + SUBCASE("Null Outputs") { + BranchOutput const null_output = edge_ref.get_null_output(); + CHECK(null_output.id == 1); + CHECK(null_output.energized == 0); + CHECK(null_output.loading == 0.0); + CHECK(null_output.i_from == 0.0); + + BranchShortCircuitOutput const null_sc_output = edge_ref.get_null_sc_output(); + CHECK(null_sc_output.energized == 0); + CHECK(null_sc_output.i_from(0) == 0.0); + } +} + +} // namespace power_grid_model diff --git a/tests/cpp_unit_tests/component/test_generic_branch.cpp b/tests/cpp_unit_tests/component/test_generic_branch.cpp index 144424c0ed..50cd4f1908 100644 --- a/tests/cpp_unit_tests/component/test_generic_branch.cpp +++ b/tests/cpp_unit_tests/component/test_generic_branch.cpp @@ -54,13 +54,12 @@ TEST_CASE("Test generic_branch") { CHECK(branch.to_node() == 3); CHECK(branch.from_status() == true); CHECK(branch.to_status() == true); - CHECK(branch.branch_status() == true); + CHECK(branch.edge_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_from)); CHECK(branch.base_i_to() == doctest::Approx(base_i_to)); CHECK(branch.phase_shift() == 0.0); - CHECK(!branch.is_param_mutable()); } SUBCASE("Symmetric parameters") { diff --git a/tests/cpp_unit_tests/component/test_line.cpp b/tests/cpp_unit_tests/component/test_line.cpp index 9f47cfdb50..c339bd8fe8 100644 --- a/tests/cpp_unit_tests/component/test_line.cpp +++ b/tests/cpp_unit_tests/component/test_line.cpp @@ -80,13 +80,12 @@ TEST_CASE("Test line") { CHECK(branch.to_node() == 3); CHECK(branch.from_status() == true); CHECK(branch.to_status() == true); - CHECK(branch.branch_status() == true); + CHECK(branch.edge_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") { @@ -257,13 +256,12 @@ TEST_CASE("Test line") { CHECK(branch_into_itself.from_status() == branch.from_status()); CHECK(branch_into_itself.to_status() == branch.to_status()); - CHECK(branch_into_itself.branch_status() == branch.branch_status()); + CHECK(branch_into_itself.edge_status() == branch.edge_status()); CHECK(branch_into_itself.status(BranchSide::from) == branch.status(BranchSide::from)); CHECK(branch_into_itself.status(BranchSide::to) == branch.status(BranchSide::to)); CHECK(branch_into_itself.base_i_from() == branch.base_i_from()); CHECK(branch_into_itself.base_i_to() == branch.base_i_to()); CHECK(branch_into_itself.phase_shift() == branch.phase_shift()); - CHECK(branch_into_itself.is_param_mutable() == branch.is_param_mutable()); SUBCASE("Symmetric parameters") { auto const params = branch_into_itself.calc_param(); diff --git a/tests/cpp_unit_tests/component/test_link.cpp b/tests/cpp_unit_tests/component/test_link.cpp index ff353bc2e8..5afabe08ad 100644 --- a/tests/cpp_unit_tests/component/test_link.cpp +++ b/tests/cpp_unit_tests/component/test_link.cpp @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: MPL-2.0 -#include #include #include @@ -13,6 +12,7 @@ #include #include #include +#include #include @@ -25,7 +25,7 @@ using namespace std::complex_literals; TEST_CASE("Test link") { LinkInput input{.id = 1, .from_node = 2, .to_node = 3, .from_status = 1, .to_status = 1}; Link link{input, 10e3, 50e3}; - Branch& branch = link; + Edge& branch = link; double const base_i_from = base_power_1p / (10.0e3 / sqrt3); double const base_i_to = base_power_1p / (50.0e3 / sqrt3); ComplexValue const uaf{1.0}; @@ -37,14 +37,13 @@ TEST_CASE("Test link") { ComplexValue const if_sc_asym{1.0 + 1.0i}; ComplexValue const it_sc_asym{2.0 + (2.0i * sqrt3)}; - CHECK(link.math_model_type() == ComponentType::branch); + CHECK(link.math_model_type() == ComponentType::link); SUBCASE("General") { 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_from)); CHECK(branch.base_i_to() == doctest::Approx(base_i_to)); - CHECK(!branch.is_param_mutable()); CHECK(branch.phase_shift() == 0.0); } diff --git a/tests/cpp_unit_tests/component/test_transformer.cpp b/tests/cpp_unit_tests/component/test_transformer.cpp index 1139bda8cb..f97ffcb95a 100644 --- a/tests/cpp_unit_tests/component/test_transformer.cpp +++ b/tests/cpp_unit_tests/component/test_transformer.cpp @@ -406,8 +406,6 @@ TEST_CASE("Test Transfomer - Test 0 YNyn12") { CHECK((cabs(param.value[2] - y_tf) < numerical_tolerance).all()); CHECK((cabs(param.value[3] - y_tt) < numerical_tolerance).all()); - SUBCASE("Test transformer is_param_mutable") { CHECK(YNyn12.is_param_mutable() == true); } - SUBCASE("Test transformer phase shift") { CHECK(YNyn12.phase_shift() == doctest::Approx(0.0)); } SUBCASE("Test transformer loading") { CHECK(YNyn12.loading(60.0e6, 0.0) == doctest::Approx(2.0)); } diff --git a/tests/cpp_unit_tests/main_core/test_main_model_type.cpp b/tests/cpp_unit_tests/main_core/test_main_model_type.cpp index 62a9292fe4..0b8bd2a57f 100644 --- a/tests/cpp_unit_tests/main_core/test_main_model_type.cpp +++ b/tests/cpp_unit_tests/main_core/test_main_model_type.cpp @@ -86,15 +86,15 @@ TEST_CASE("MainModelType") { CHECK(calls == std::vector{"node", "source"}); } SUBCASE("Node Line Source") { - using ModelType = - MainModelType, ComponentList>; + using ModelType = MainModelType, + ComponentList>; static_assert( std::is_same_v, Node, Line, Source>>); + Container, Node, Line, Source>>); static_assert(std::is_same_v>); - static_assert(std::is_same_v>); - static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); static_assert(ModelType::index_of_component == 0); static_assert(ModelType::index_of_component == 1); static_assert(ModelType::index_of_component == 2); @@ -115,18 +115,18 @@ TEST_CASE("MainModelType") { utils::run_functor_with_tuple_return_void( [&calls]() { calls.push_back(std::string_view(CompType::name)); }); - CHECK(calls == std::vector{"node", "branch", "source"}); + CHECK(calls == std::vector{"node", "edge", "source"}); } SUBCASE("Different component order: Line Source Node") { - using ModelType = - MainModelType, ComponentList>; + using ModelType = MainModelType, + ComponentList>; static_assert( std::is_same_v, Line, Source, Node>>); + Container, Line, Source, Node>>); static_assert(std::is_same_v>); - static_assert(std::is_same_v>); - static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); static_assert(ModelType::index_of_component == 0); static_assert(ModelType::index_of_component == 1); static_assert(ModelType::index_of_component == 2); @@ -148,7 +148,7 @@ TEST_CASE("MainModelType") { utils::run_functor_with_tuple_return_void( [&calls]() { calls.push_back(std::string_view(CompType::name)); }); - CHECK(calls == std::vector{"node", "branch", "source"}); + CHECK(calls == std::vector{"node", "edge", "source"}); } SUBCASE("Node AComponent Source") { diff --git a/tests/cpp_unit_tests/optimizer/test_optimizer.hpp b/tests/cpp_unit_tests/optimizer/test_optimizer.hpp index e7f410be98..8ede89e0b9 100644 --- a/tests/cpp_unit_tests/optimizer/test_optimizer.hpp +++ b/tests/cpp_unit_tests/optimizer/test_optimizer.hpp @@ -84,8 +84,8 @@ inline auto u_pu(State const& /* state */, std::vector const& // using StubComponentContainer = Container, StubComponent, StubTransformerA, // TransformerTapRegulator, StubTransformerB>; using StubComponentContainer = - Container, Line, Link, Node, Transformer, - ThreeWindingTransformer, TransformerTapRegulator, Source>; + Container, Line, Link, Node, + Transformer, ThreeWindingTransformer, TransformerTapRegulator, Source>; using StubState = main_core::MainModelState; static_assert(main_core::main_model_state_c);