Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 39 additions & 11 deletions docs/user_manual/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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.
Comment thread
mgovers marked this conversation as resolved.
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.}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -40,7 +41,7 @@ using AllComponents = ComponentList<Node, Line, AsymLine, Link, GenericBranch, T
AsymCurrentSensor, Fault, TransformerTapRegulator, VoltageRegulator>;

using AllExtraRetrievableTypes =
ExtraRetrievableTypes<Base, Node, Branch, Branch3, Appliance, GenericLoadGen, GenericLoad, GenericGenerator,
ExtraRetrievableTypes<Base, Node, Edge, Branch, Branch3, Appliance, GenericLoadGen, GenericLoad, GenericGenerator,
GenericPowerSensor, GenericVoltageSensor, GenericCurrentSensor, Regulator>;

} // namespace power_grid_model
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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_{};
Expand Down Expand Up @@ -103,7 +102,7 @@ class AsymLine : public Branch {
BranchCalcParam<asymmetric_t> asym_calc_param() const final {
BranchCalcParam<asymmetric_t> 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#pragma once

#include "base.hpp"
#include "edge.hpp"

#include "../auxiliary/input.hpp"
#include "../auxiliary/output.hpp"
Expand All @@ -25,233 +26,16 @@

namespace power_grid_model {

class Branch : public Base {
class Branch : public Edge { // TODO: not yet unit tested

Check warning on line 29 in power_grid_model_c/power_grid_model/include/power_grid_model/component/branch.hpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Complete the task associated to this "TODO" comment.

See more on https://sonarcloud.io/project/issues?id=PowerGridModel_power-grid-model&issues=AaApCR6TObA3UIzKM_u6&open=AaApCR6TObA3UIzKM_u6&pullRequest=1546
public:
using InputType = BranchInput;
using UpdateType = BranchUpdate;
template <symmetry_tag sym> using OutputType = BranchOutput<sym>;
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<bool>(branch_input.from_status)},
to_status_{static_cast<bool>(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 <symmetry_tag sym> BranchCalcParam<sym> calc_param(bool is_connected_to_source = true) const {
if (!energized(is_connected_to_source)) {
return BranchCalcParam<sym>{};
}
if constexpr (is_symmetric_v<sym>) {
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 <symmetry_tag sym>
BranchOutput<sym> get_output(BranchSolverOutput<sym> const& branch_solver_output) const {
// result object
BranchOutput<sym> output{};
static_cast<BaseOutput&>(output) = base_output(true);
// calculate result
output.p_from = base_power<sym> * real(branch_solver_output.s_f);
output.q_from = base_power<sym> * imag(branch_solver_output.s_f);
output.i_from = base_i_from() * cabs(branch_solver_output.i_f);
output.s_from = base_power<sym> * cabs(branch_solver_output.s_f);
output.p_to = base_power<sym> * real(branch_solver_output.s_t);
output.q_to = base_power<sym> * imag(branch_solver_output.s_t);
output.i_to = base_i_to() * cabs(branch_solver_output.i_t);
output.s_to = base_power<sym> * 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<symmetric_t> const& i_f,
ComplexValue<symmetric_t> const& i_t) const {
return get_sc_output(BranchShortCircuitSolverOutput<symmetric_t>{.i_f = i_f, .i_t = i_t});
}
BranchShortCircuitOutput get_sc_output(ComplexValue<asymmetric_t> const& i_f,
ComplexValue<asymmetric_t> const& i_t) const {
return get_sc_output(BranchShortCircuitSolverOutput<asymmetric_t>{.i_f = i_f, .i_t = i_t});
}

BranchShortCircuitOutput
get_sc_output(BranchShortCircuitSolverOutput<asymmetric_t> const& branch_solver_output) const {
BranchShortCircuitOutput output{};
static_cast<BaseOutput&>(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<symmetric_t> const& branch_solver_output) const {
return get_sc_output(
BranchShortCircuitSolverOutput<asymmetric_t>{.i_f = ComplexValue<asymmetric_t>{branch_solver_output.i_f},
.i_t = ComplexValue<asymmetric_t>{branch_solver_output.i_t}});
}

template <symmetry_tag sym> BranchOutput<sym> get_null_output() const {
BranchOutput<sym> output{.loading = {},
.p_from = {},
.q_from = {},
.i_from = {},
.s_from = {},
.p_to = {},
.q_to = {},
.i_to = {},
.s_to = {}};
static_cast<BaseOutput&>(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<BaseOutput&>(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<bool>(new_from_status));
from_status_ = static_cast<bool>(new_from_status);
}
if (set_to) {
changed = changed || (to_status_ != static_cast<bool>(new_to_status));
to_status_ = static_cast<bool>(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<BranchUpdate> 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<symmetric_t>
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<symmetric_t> 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<asymmetric_t> 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<symmetric_t> const param1 = calc_param_y_sym(y1_series, y1_shunt, tap_ratio);
BranchCalcParam<symmetric_t> 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<asymmetric_t> param{};
for (size_t i = 0; i < 4; ++i) {
param.value[i] = ComplexTensor<asymmetric_t>{(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<symmetric_t> sym_calc_param() const = 0;
virtual BranchCalcParam<asymmetric_t> asym_calc_param() const = 0;
using Edge::Edge; // inherit constructor
};

} // namespace power_grid_model
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ static_assert(is_generator_v<gen_appliance_t>);
// Forward declarations of all components
class Base;
class Node;
class Edge;
class Branch;
class Branch3;
class Appliance;
Expand Down
Loading
Loading